Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

Commit 33c7906

Browse files
committed
Merging develop to master in preparation for 1.1.0 release.
2 parents cc8dd1e + a698fe2 commit 33c7906

12 files changed

+483
-18
lines changed

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,33 @@ All notable changes to this project will be documented in this file, in reverse
44

55
Versions prior to 0.4.0 were released as the package "weierophinney/hal".
66

7+
## 1.1.0 - 2018-06-05
8+
9+
### Added
10+
11+
- [#39](https://github.com/zendframework/zend-expressive-hal/pull/39) adds a cookbook recipe detailing how to create a fully contained, path-segregated
12+
module, complete with its own router, capable of generating HAL resources.
13+
14+
### Changed
15+
16+
- [#39](https://github.com/zendframework/zend-expressive-hal/pull/39) updates `LinkGeneratorFactory` to allow passing an alternate service name to use when
17+
retrieving the `LinkGenerator\UriGeneratorInterface` dependency.
18+
19+
- [#39](https://github.com/zendframework/zend-expressive-hal/pull/39) updates `ResourceGeneratorFactory` to allow passing an alternate service name to use when
20+
retrieving the `LinkGenerator` dependency.
21+
22+
### Deprecated
23+
24+
- Nothing.
25+
26+
### Removed
27+
28+
- Nothing.
29+
30+
### Fixed
31+
32+
- Nothing.
33+
734
## 1.0.3 - TBD
835

936
### Added

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@
6161
},
6262
"extra": {
6363
"branch-alias": {
64-
"dev-master": "1.0.x-dev",
65-
"dev-develop": "1.1.x-dev"
64+
"dev-master": "1.1.x-dev",
65+
"dev-develop": "1.2.x-dev"
6666
},
6767
"zf": {
6868
"config-provider": "Zend\\Expressive\\Hal\\ConfigProvider"

composer.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
# Using the ResourceGenerator in path-segregated middleware
2+
3+
- Since 1.1.0.
4+
5+
You may want to develop your API as a separate module that you can then drop in
6+
to an existing application; you may even want to [path-segregate](https://docs.zendframework.com/zend-expressive/v3/features/router/piping/#path-segregation) it.
7+
8+
In such cases, you will want to use a different router instance, which has a
9+
huge number of ramifications:
10+
11+
- You'll need separate routing middleware.
12+
- You'll need a separate [UrlHelper](https://docs.zendframework.com/zend-expressive/v3/features/helpers/url-helper/) instance, as well as its related middleware.
13+
- You'll need a separate URL generator for HAL that consumes the separate
14+
`UrlHelper` instance.
15+
- You'll need a separate `LinkGenerator` for HAL that consumes the separate URL
16+
generator.
17+
- You'll need a separate `ResourceGenerator` for HAL that consumes the separate
18+
`LinkGenerator`.
19+
20+
This can be accomplished by writing your own factories, but that means a lot of
21+
extra code, and the potential for it to go out-of-sync with the official
22+
factories for these services. What should you do?
23+
24+
## Virtual services
25+
26+
Since version 1.1.0 of this package, and versions 3.1.0 of
27+
zend-expressive-router and 5.1.0 of zend-expressive-helpers, you can now pass
28+
additional constructor arguments to a number of factories to allow varying the
29+
service dependencies they look for.
30+
31+
In our example below, we will create an `Api` module. This module will have its
32+
own router, and be segregated in the path `/api`; all routes we create will be
33+
relative to that path, and not include it in their definitions. The handler we
34+
create will return HAL-JSON, and thus need to generate links using the
35+
configured router and base path.
36+
37+
To begin, we will alter the `ConfigProvider` for our module to add the
38+
definitions noted below:
39+
40+
```php
41+
// in src/Api/ConfigProvider.php:
42+
namespace Api;
43+
44+
use Zend\Expressive\Hal\LinkGeneratorFactory;
45+
use Zend\Expressive\Hal\LinkGenerator\ExpressiveUrlGeneratorFactory;
46+
use Zend\Expressive\Hal\Metadata\MetadataMap;
47+
use Zend\Expressive\Hal\ResourceGeneratorFactory;
48+
use Zend\Expressive\Helper\UrlHelperFactory;
49+
use Zend\Expressive\Helper\UrlHelperMiddlewareFactory;
50+
use Zend\Expressive\Router\FastRouteRouter;
51+
use Zend\Expressive\Router\Middleware\RouteMiddlewareFactory;
52+
53+
class ConfigProvider
54+
{
55+
public function __invoke() : array
56+
{
57+
return [
58+
'dependencies' => $this->getDependencies(),
59+
MetadataMap::class => $this->getMetadataMap(),
60+
];
61+
}
62+
63+
public function getDependencies() : array
64+
{
65+
return [
66+
'factories' => [
67+
// module-specific class name => factory
68+
LinkGenerator::class => new LinkGeneratorFactory(UrlGenerator::class),
69+
ResourceGenerator::class => new ResourceGeneratorFactory(LinkGenerator::class),
70+
Router::class => FastRouteRouterFactory::class,
71+
UrlHelper::class => new UrlHelperFactory('/api', Router::class),
72+
UrlHelperMiddleware::class => new UrlHelperMiddlewareFactory(UrlHelper::class),
73+
UrlGenerator::class => new ExpressiveUrlGeneratorFactory(UrlHelper::class),
74+
75+
// Our handler:
76+
CreateBookHandler::class => CreateBookHandlerFactory::class,
77+
78+
// And our pipeline:
79+
Pipeline::class => PipelineFactory::class,
80+
],
81+
];
82+
}
83+
84+
public function getMetadataMap() : array
85+
{
86+
return [
87+
// ...
88+
];
89+
}
90+
}
91+
```
92+
93+
Note that the majority of these service names are _virtual_; they do not resolve
94+
to actual classes. PHP allows usage of the `::class` pseudo-constant anywhere,
95+
and will resolve the value based on the current namespace. This gives us virtual
96+
services such as `Api\Router`, `Api\UrlHelper`, etc.
97+
98+
Also note that we are creating factory _instances_. Normally, we recommend not
99+
using closures or instances for factories due to potential problems with
100+
configuration caching. Fortunately, we have provided functionality in each of
101+
these factories that allows them to be safely cached, retaining the
102+
context-specific configuration required.
103+
104+
> ### What about the hard-coded path?
105+
>
106+
> You'll note that the above example hard-codes the base path for the
107+
> `UrlHelper`. What if you want to use a different path?
108+
>
109+
> You can override the service in an application-specific configuration under
110+
> `config/autoload/`, specifying a different path!
111+
>
112+
> ```php
113+
> \Api\UrlHelper::class => new UrlHelperFactory('/different/path', \Api\Router::class),
114+
> ```
115+
116+
## Using virtual services with a handler
117+
118+
Now let's turn to our `CreateBookHandler`. We'll define it as follows:
119+
120+
```php
121+
// in src/Api/CreateBookHandler.php:
122+
namespace Api;
123+
124+
use Psr\Http\Message\ResponseInterface;
125+
use Psr\Http\Message\ServerRequestInterface;
126+
use Psr\Http\Server\RequestHandlerInterface;
127+
use Zend\Expressive\Hal\HalResponseFactory;
128+
use Zend\Expressive\Hal\ResourceGenerator;
129+
130+
class CreateBookHandler implements RequestHandlerInterface
131+
{
132+
private $resourceGenerator;
133+
134+
private $responseFactory;
135+
136+
public function __construct(ResourceGenerator $resourceGenerator, HalResponseFactory $responseFactory)
137+
{
138+
$this->resourceGenerator = $resourceGenerator;
139+
$this->responseFactory = $responseFactory;
140+
}
141+
142+
public function handle(ServerRequestInterface $request) : ResponseInterface
143+
{
144+
// do some work ...
145+
146+
$resource = $this->resourceGenerator->fromObject($book, $request);
147+
return $this->responseFactory->createResponse($request, $book);
148+
}
149+
}
150+
```
151+
152+
This handler needs a HAL resource generator. More specifically, it needs the one
153+
specific to our module. As such, we'll define our factory as follows:
154+
155+
```php
156+
// in src/Api/CreateBookHandlerFactory.php:
157+
namespace Api;
158+
159+
use Psr\Container\ContainerInterface;
160+
use Zend\Expressive\Hal\HalResponseFactory;
161+
162+
class CreateBookHandlerFactory
163+
{
164+
public function __invoke(ContainerInterface $container) : CreateBookHandler
165+
{
166+
return new CreateBookHandler(
167+
ResourceGenerator::class, // module-specific service name!
168+
HalResponseFactory::class
169+
);
170+
}
171+
}
172+
```
173+
174+
You can create any number of such handlers for your module; the above
175+
demonstrates how and where injection of the alternate resource generator occurs.
176+
177+
## Creating our pipeline and routes
178+
179+
Now we can create our pipeline and routes.
180+
181+
Generally when piping to an application instance, we can specify a class name of
182+
middleware to pipe, or an array of middleware:
183+
184+
```php
185+
// in config/pipeline.php:
186+
$app->pipe('/api', [
187+
\Zend\ProblemDetails\ProblemDetailsMiddleware::class,
188+
\Api\RouteMiddleware::class, // module-specific routing middleware!
189+
ImplicitHeadMiddleware::class,
190+
ImplicitOptionsMiddleware::class,
191+
MethodNotAllowedMiddleware::class,
192+
\Api\UrlHelperMiddleware::class, // module-specific URL helper middleware!
193+
DispatchMiddleware::class,
194+
\Zend\ProblemDetails\ProblemDetailsNotFoundHandler::class,
195+
]);
196+
```
197+
198+
However, we have both the pipeline _and_ routes, and we likely want to indicate
199+
the exact behavior of this pipeline. Additionally, we may want to re-use this
200+
pipeline in other applications; pushing this into the application configuration
201+
makes that more error-prone.
202+
203+
As such, we will create a factory that generates and returns a
204+
`Zend\Stratigility\MiddlewarePipe` instance that is fully configured for our
205+
module. As part of this functionality, we will also add our module-specific
206+
routing.
207+
208+
```php
209+
// In src/Api/PipelineFactory.php:
210+
namespace Api;
211+
212+
use Psr\Container\ContainerInterface;
213+
use Zend\Expressive\MiddlewareFactory;
214+
use Zend\Expressive\Router\Middleware as RouterMiddleware;
215+
use Zend\ProblemDetails\ProblemDetailsMiddleware;
216+
use Zend\ProblemDetails\ProblemDetailsNotFoundHandler;
217+
use Zend\Stratigility\MiddlewarePipe;
218+
219+
class PipelineFactory
220+
{
221+
public function __invoke(ContainerInterface $container) : MiddlewarePipe
222+
{
223+
$factory = $container->get(MiddlewareFactory::class);
224+
225+
// First, create our middleware pipeline
226+
$pipeline = new MiddlewarePipe();
227+
$pipeline->pipe($factory->lazy(ProblemDetailsMiddleware::class));
228+
$pipeline->pipe($factory->lazy(RouteMiddleware::class)); // module-specific!
229+
$pipeline->pipe($factory->lazy(RouterMiddleware\ImplicitHeadMiddleware::class));
230+
$pipeline->pipe($factory->lazy(RouterMiddleware\ImplicitOptionsMiddleware::class));
231+
$pipeline->pipe($factory->lazy(RouterMiddleware\MethodNotAllowedMiddleware::class));
232+
$pipeline->pipe($factory->lazy(UrlHelperMiddlweare::class)); // module-specific!
233+
$pipeline->pipe($factory->lazy(RouterMiddleware\DispatchMiddleware::class));
234+
$pipeline->pipe($factory->lazy(ProblemDetailsNotFoundHandler::class));
235+
236+
// Second, we'll create our routes
237+
$router = $container->get(Router::class); // Retrieve our module-specific router
238+
$routes = new RouteCollector($router); // Create a route collector to simplify routing
239+
240+
// Start routing:
241+
$routes->post('/books', $factory->lazy(CreateBookHandler::class));
242+
243+
// Return the pipeline now that we're done!
244+
return $pipeline;
245+
}
246+
}
247+
```
248+
249+
Note that the routing definitions do **not** include the prefix `/api`; this is
250+
because that prefix will be stripped when we path-segregate our API middleware
251+
pipeline. All routing will be _relative_ to that path.
252+
253+
## Creating a path-segregated pipeline
254+
255+
Finally, we will attach our pipeline to the application, using path segregation:
256+
257+
```php
258+
// in config/pipeline.php:
259+
$app->pipe('/api', \Api\Pipeline::class);
260+
```
261+
262+
This statement tells the application to pipe the pipeline returned by our
263+
`PipelineFactory` under the path `/api`; that path will be stripped from
264+
requests when passed to the underlying middleware.
265+
266+
At this point, we now have a re-usable module, complete with its own routing,
267+
with URI generation that will include the base path under which we have
268+
segregated the pipeline!

docs/book/factories.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,15 @@ configured instances for your use.
2525
- Registered as service: `Zend\Expressive\Hal\LinkGenerator`
2626
- Generates instance of: `Zend\Expressive\Hal\LinkGenerator`
2727
- Depends on:
28-
- `Zend\Expressive\Hal\LinkGenerator\UrlGenerator` service
28+
- `Zend\Expressive\Hal\LinkGenerator\UrlGeneratorInterface` service
29+
30+
Since version 1.1.0, this factory allows an optional constructor argument,
31+
`$urlGeneratorServiceName`. It defaults to
32+
`Zend\Expressive\Hal\LinkGenerator\UrlGeneratorInterface`,
33+
but you may specify an alternate service if desired. This may be useful, for
34+
instance, when using an alternate router in a path-segregated middleware
35+
pipeline, which would necessitate a different `UrlHelper` instance, and an
36+
alternate URL generator that consumes it.
2937

3038
## Zend\Expressive\Hal\LinkGenerator\ExpressiveUrlGeneratorFactory
3139

@@ -37,6 +45,12 @@ configured instances for your use.
3745
- `Zend\Expressive\Helper\ServerUrlHelper` service (optional; if not provided,
3846
URIs will be generated without authority information)
3947

48+
Since version 1.1.0, this factory allows an optional constructor argument, `$urlHelperServiceName`.
49+
It defaults to `Zend\Expressive\Helper\UrlHelper`, but you may specify an
50+
alternate service if desired. This may be useful, for instance, when using an
51+
alternate router in a path-segregated middleware pipeline, which would
52+
necessitate a different `UrlHelper` instance.
53+
4054
## Zend\Expressive\Hal\LinkGenerator\UrlGeneratorInterface
4155

4256
- Registered as service: `Zend\Expressive\Hal\LinkGenerator\UrlGeneratorInterface`
@@ -142,3 +156,10 @@ the namespace.
142156
If you wish to use a container implementation other than the
143157
`Zend\Hydrator\HydratorPluginManager`, either register it under that service
144158
name, or create an alternate factory.
159+
160+
Since version 1.1.0, this factory allows an optional constructor argument, `$linkGeneratorServiceName`.
161+
It defaults to `Zend\Expressive\Hal\LinkGenerator`, but you may specify an
162+
alternate service if desired. This may be useful, for instance, when using an
163+
alternate router in a path-segregated middleware pipeline, which would
164+
necessitate a different `UrlHelper` instance, an alternate URL generator that
165+
consumes it, and an alternate `LinkGenerator` consuming the URL generator.

mkdocs.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ pages:
1212
- "Factories": factories.md
1313
- Cookbook:
1414
- "Generating Custom Links In Middleware and Request Handlers": cookbook/generating-custom-links-in-middleware.md
15+
- "Using the ResourceGenerator in path-segregated middleware": cookbook/path-segregated-uri-generation.md
1516
site_name: Hypertext Application Language
1617
site_description: 'Hypertext Application Language for PSR-7 Applications'
1718
repo_url: 'https://github.com/zendframework/zend-expressive-hal'
18-
copyright: 'Copyright (c) 2017 <a href="http://www.zend.com/">Zend Technologies USA Inc.</a>'
19+
copyright: 'Copyright (c) 2017-2018 <a href="http://www.zend.com/">Zend Technologies USA Inc.</a>'

0 commit comments

Comments
 (0)