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 0000000..09499b8 Binary files /dev/null and b/app/public/favicon.ico differ 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 0000000..09499b8 Binary files /dev/null and b/implementation/10-invoker/public/favicon.ico differ 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 @@ +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 @@ +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 @@ +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