Skip to content

Commit c69f1a8

Browse files
authored
Rewrite exercise to use league/route and symfony/string (#106)
* Rewrite exercise to use league/route and symfony/string * Update failure class * Delete & ignore lock files * Don't store solution composer.lock files
1 parent ba7dadb commit c69f1a8

File tree

16 files changed

+317
-228
lines changed

16 files changed

+317
-228
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,7 @@ program.php
55
program.js
66
/solution
77
exercises/*/solution/vendor
8+
exercises/*/solution/composer.lock
9+
/test/solutions/*/*/vendor
10+
/test/solutions/*/*/composer.lock
811
.phpunit.result.cache

composer.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,10 @@
4141
"unit-tests": "phpunit",
4242
"cs" : [
4343
"phpcs src --standard=PSR12",
44-
"phpcs test --standard=PSR12"
45-
],
44+
"phpcs test --standard=PSR12 --ignore='test/solutions'" ],
4645
"cs-fix" : [
4746
"phpcbf src --standard=PSR12 --encoding=UTF-8",
48-
"phpcbf test --standard=PSR12 --encoding=UTF-8"
47+
"phpcbf test --standard=PSR12 --encoding=UTF-8 --ignore='test/solutions'"
4948
],
5049
"static": "phpstan --ansi analyse --level max src"
5150
}

exercises/dependency-heaven/problem/problem.md

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Write an HTTP **server** that serves JSON data when it receives a POST request to `/reverse`, `/swapcase` and `/titleize`.
1+
Write an HTTP **server** that serves JSON data when it receives a POST request to `/reverse`, `/snake` and `/titleize`.
22

33
The POST data will contain a single parameter `data` which you will need to manipulate depending on the endpoint.
44

@@ -12,13 +12,13 @@ A request with `data = "PHP School is awesome!"` should return the response:
1212
}
1313
```
1414

15-
### /swapcase
15+
### /snake
1616

1717
A request with `data = "No, It Really Is..."` should return the response:
1818

1919
```json
2020
{
21-
"result": "nO, iT rEALLY iS..."
21+
"result": "no_it_really_is"
2222
}
2323
```
2424

@@ -32,9 +32,61 @@ A request with `data = "you know you love it, don't you?"` should return the res
3232
}
3333
```
3434

35-
You should use the routing library `klein/klein` for this task pulling it in as a dependency through Composer.
35+
You should use the routing library `league/route` for this task, pulling it in as a dependency through Composer. `league/route` allows us to define actions which respond to requests based on the request URI and HTTP method.
3636

37-
You will also be required to use `danielstjules/stringy` to manipulate the data as this correctly handles multibyte characters.
37+
The library works by accepting a PSR7 request and returns to you a PSR7 response. It is up to you to marshal the request object and output the response to the browser.
38+
39+
There are a few other components we need, in order to use `league/route`:
40+
41+
* **laminas/laminas-diactoros** - For the PSR7 requests and responses.
42+
* **laminas/laminas-httphandlerrunner** - For outputting the PSR7 response to the browser.
43+
44+
`laminas/laminas-diactoros` is a PSR7 implementation. PSR's are standards defined by the PHP-FIG, a committee of PHP projects, attempting to increase interoperability in the PHP ecosystem. PSR7 is a standard for modelling HTTP requests. We can use the `laminas/laminas-diactoros` package to marshal a PSR7 request object from the PHP super globals like so:
45+
46+
```php
47+
$request = \Laminas\Diactoros\ServerRequestFactory::fromGlobals();
48+
```
49+
50+
`$request` is now a PSR7 request, that can be used with `league/route`.
51+
52+
`laminas/laminas-httphandlerrunner` provides a simple method to output the PSR7 response to the browser, handling headers, status codes and the content. Use it like so:
53+
54+
```php
55+
(new \Laminas\HttpHandlerRunner\Emitter\SapiEmitter())->emit($response);
56+
```
57+
58+
Where `$response` is a PSR7 response.
59+
60+
In between this, you will need to define your routes and execute the dispatching mechanism to receive a response. Refer to the `league\route` [documentation](https://route.thephpleague.com/5.x/usage/).
61+
62+
Each route action will be passed the PSR7 request where you can access the request parameters and body. To access the `data` key from the request body, you would use the following:
63+
64+
```php
65+
$data = $request->getParsedBody()['data'] ?? '';
66+
```
67+
68+
In each action, you are expected to return a PSR7 response. `laminas/laminas-diactoros` provides a few ways to accomplish this:
69+
70+
A text response:
71+
72+
```php
73+
$response = new \Laminas\Diactoros\Response('My Content');
74+
```
75+
76+
A JSON response:
77+
78+
```php
79+
$response = (new \Laminas\Diactoros\Response(json_encode(['desert' => 'cookies'])))
80+
->withAddedHeader('Content-Type', 'application/json; charset=utf-8'');
81+
```
82+
83+
Or you could use the helper class, which takes care of encoding and setting the correct headers:
84+
85+
```php
86+
$response = (new \Laminas\Diactoros\Response\JsonResponse(['desert' => 'cookies']));
87+
```
88+
89+
Finally, you will also be required to use `symfony/string` to manipulate the data as this correctly handles multibyte characters.
3890

3991
----------------------------------------------------------------------
4092
## HINTS
@@ -46,8 +98,10 @@ Use `composer init` to create your `composer.json` file with interactive search.
4698
For more details look at the docs for...
4799

48100
* **Composer** - [https://getcomposer.org/doc/01-basic-usage.md](https://getcomposer.org/doc/01-basic-usage.md)
49-
* **Klein** - [https://github.com/chriso/klein.php](https://github.com/chriso/klein.php)
50-
* **Stringy** - [https://github.com/danielstjules/Stringy](https://github.com/danielstjules/Stringy)
101+
* **League Route** - [https://route.thephpleague.com/5.x/usage/](https://route.thephpleague.com/5.x/usage/)
102+
* **Symfony String** - [https://symfony.com/doc/current/components/string.html](https://symfony.com/doc/current/components/string.html)
103+
* **PSR** - [https://www.php-fig.org/psr/](https://www.php-fig.org/psr/)
104+
* **PSR 7** - [https://www.php-fig.org/psr/psr-7/](https://www.php-fig.org/psr/psr-7/)
51105

52106
Oh, and don't forget to use the Composer autoloader with:
53107

exercises/dependency-heaven/solution/composer.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
"name": "learn-you-php/dependency-heaven",
33
"description": "String manipulation with Composer",
44
"require": {
5-
"danielstjules/stringy": "^2.1",
6-
"klein/klein": "^2.1"
5+
"symfony/string": "^5.0 | ^6.0",
6+
"league/route": "^5.1",
7+
"laminas/laminas-diactoros": "^2.4",
8+
"laminas/laminas-httphandlerrunner": "^1.2 | ^2.4"
79
},
810
"authors": [
911
{

exercises/dependency-heaven/solution/composer.lock

Lines changed: 0 additions & 189 deletions
This file was deleted.
Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,45 @@
11
<?php
22

3-
// Require composer autoloader
43
require __DIR__ . '/vendor/autoload.php';
54

6-
use Klein\Klein;
7-
use Klein\Request;
8-
use Klein\Response;
9-
use Stringy\Stringy as S;
5+
use Laminas\Diactoros\Response\JsonResponse;
6+
use Laminas\Diactoros\ServerRequestFactory;
7+
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
8+
use League\Route\Router;
9+
use Psr\Http\Message\ResponseInterface;
10+
use Psr\Http\Message\ServerRequestInterface;
1011

11-
$klein = new Klein();
12+
use function Symfony\Component\String\s;
1213

13-
$klein->respond('POST', '/reverse', function (Request $req, Response $res) {
14-
$res->json(['result' => (new S($req->param('data', '')))->reverse()->__toString()]);
14+
$request = ServerRequestFactory::fromGlobals();
15+
16+
$router = new Router();
17+
18+
$router->post('/reverse', function (ServerRequestInterface $request): ResponseInterface {
19+
$str = $request->getParsedBody()['data'] ?? '';
20+
21+
return new JsonResponse([
22+
'result' => s($str)->reverse()->toString()
23+
]);
1524
});
1625

17-
$klein->respond('POST', '/swapcase', function (Request $req, Response $res) {
18-
$res->json(['result' => (new S($req->param('data', '')))->swapCase()->__toString()]);
26+
$router->post('/snake', function (ServerRequestInterface $request): ResponseInterface {
27+
$str = $request->getParsedBody()['data'] ?? '';
28+
29+
return new JsonResponse([
30+
'result' => s($str)->snake()->toString()
31+
]);
1932
});
2033

21-
$klein->respond('POST', '/titleize', function (Request $req, Response $res) {
22-
$res->json(['result' => (new S($req->param('data', '')))->titleize()->__toString()]);
34+
35+
$router->post('/titleize', function (ServerRequestInterface $request): ResponseInterface {
36+
$str = $request->getParsedBody()['data'] ?? '';
37+
38+
return new JsonResponse([
39+
'result' => s($str)->title(true)->toString()
40+
]);
2341
});
2442

25-
$klein->dispatch();
43+
$response = $router->dispatch($request);
44+
45+
(new SapiEmitter())->emit($response);

0 commit comments

Comments
 (0)