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

Commit

Permalink
Merging develop to master in preparation for 1.1.0 release.
Browse files Browse the repository at this point in the history
  • Loading branch information
weierophinney committed Jun 5, 2018
2 parents cc8dd1e + a698fe2 commit 33c7906
Show file tree
Hide file tree
Showing 12 changed files with 483 additions and 18 deletions.
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@ All notable changes to this project will be documented in this file, in reverse

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

## 1.1.0 - 2018-06-05

### Added

- [#39](https://github.com/zendframework/zend-expressive-hal/pull/39) adds a cookbook recipe detailing how to create a fully contained, path-segregated
module, complete with its own router, capable of generating HAL resources.

### Changed

- [#39](https://github.com/zendframework/zend-expressive-hal/pull/39) updates `LinkGeneratorFactory` to allow passing an alternate service name to use when
retrieving the `LinkGenerator\UriGeneratorInterface` dependency.

- [#39](https://github.com/zendframework/zend-expressive-hal/pull/39) updates `ResourceGeneratorFactory` to allow passing an alternate service name to use when
retrieving the `LinkGenerator` dependency.

### Deprecated

- Nothing.

### Removed

- Nothing.

### Fixed

- Nothing.

## 1.0.3 - TBD

### Added
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev",
"dev-develop": "1.1.x-dev"
"dev-master": "1.1.x-dev",
"dev-develop": "1.2.x-dev"
},
"zf": {
"config-provider": "Zend\\Expressive\\Hal\\ConfigProvider"
Expand Down
4 changes: 2 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

268 changes: 268 additions & 0 deletions docs/book/cookbook/path-segregated-uri-generation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
# Using the ResourceGenerator in path-segregated middleware

- Since 1.1.0.

You may want to develop your API as a separate module that you can then drop in
to an existing application; you may even want to [path-segregate](https://docs.zendframework.com/zend-expressive/v3/features/router/piping/#path-segregation) it.

In such cases, you will want to use a different router instance, which has a
huge number of ramifications:

- You'll need separate routing middleware.
- You'll need a separate [UrlHelper](https://docs.zendframework.com/zend-expressive/v3/features/helpers/url-helper/) instance, as well as its related middleware.
- You'll need a separate URL generator for HAL that consumes the separate
`UrlHelper` instance.
- You'll need a separate `LinkGenerator` for HAL that consumes the separate URL
generator.
- You'll need a separate `ResourceGenerator` for HAL that consumes the separate
`LinkGenerator`.

This can be accomplished by writing your own factories, but that means a lot of
extra code, and the potential for it to go out-of-sync with the official
factories for these services. What should you do?

## Virtual services

Since version 1.1.0 of this package, and versions 3.1.0 of
zend-expressive-router and 5.1.0 of zend-expressive-helpers, you can now pass
additional constructor arguments to a number of factories to allow varying the
service dependencies they look for.

In our example below, we will create an `Api` module. This module will have its
own router, and be segregated in the path `/api`; all routes we create will be
relative to that path, and not include it in their definitions. The handler we
create will return HAL-JSON, and thus need to generate links using the
configured router and base path.

To begin, we will alter the `ConfigProvider` for our module to add the
definitions noted below:

```php
// in src/Api/ConfigProvider.php:
namespace Api;

use Zend\Expressive\Hal\LinkGeneratorFactory;
use Zend\Expressive\Hal\LinkGenerator\ExpressiveUrlGeneratorFactory;
use Zend\Expressive\Hal\Metadata\MetadataMap;
use Zend\Expressive\Hal\ResourceGeneratorFactory;
use Zend\Expressive\Helper\UrlHelperFactory;
use Zend\Expressive\Helper\UrlHelperMiddlewareFactory;
use Zend\Expressive\Router\FastRouteRouter;
use Zend\Expressive\Router\Middleware\RouteMiddlewareFactory;

class ConfigProvider
{
public function __invoke() : array
{
return [
'dependencies' => $this->getDependencies(),
MetadataMap::class => $this->getMetadataMap(),
];
}

public function getDependencies() : array
{
return [
'factories' => [
// module-specific class name => factory
LinkGenerator::class => new LinkGeneratorFactory(UrlGenerator::class),
ResourceGenerator::class => new ResourceGeneratorFactory(LinkGenerator::class),
Router::class => FastRouteRouterFactory::class,
UrlHelper::class => new UrlHelperFactory('/api', Router::class),
UrlHelperMiddleware::class => new UrlHelperMiddlewareFactory(UrlHelper::class),
UrlGenerator::class => new ExpressiveUrlGeneratorFactory(UrlHelper::class),

// Our handler:
CreateBookHandler::class => CreateBookHandlerFactory::class,

// And our pipeline:
Pipeline::class => PipelineFactory::class,
],
];
}

public function getMetadataMap() : array
{
return [
// ...
];
}
}
```

Note that the majority of these service names are _virtual_; they do not resolve
to actual classes. PHP allows usage of the `::class` pseudo-constant anywhere,
and will resolve the value based on the current namespace. This gives us virtual
services such as `Api\Router`, `Api\UrlHelper`, etc.

Also note that we are creating factory _instances_. Normally, we recommend not
using closures or instances for factories due to potential problems with
configuration caching. Fortunately, we have provided functionality in each of
these factories that allows them to be safely cached, retaining the
context-specific configuration required.

> ### What about the hard-coded path?
>
> You'll note that the above example hard-codes the base path for the
> `UrlHelper`. What if you want to use a different path?
>
> You can override the service in an application-specific configuration under
> `config/autoload/`, specifying a different path!
>
> ```php
> \Api\UrlHelper::class => new UrlHelperFactory('/different/path', \Api\Router::class),
> ```
## Using virtual services with a handler
Now let's turn to our `CreateBookHandler`. We'll define it as follows:
```php
// in src/Api/CreateBookHandler.php:
namespace Api;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Expressive\Hal\HalResponseFactory;
use Zend\Expressive\Hal\ResourceGenerator;
class CreateBookHandler implements RequestHandlerInterface
{
private $resourceGenerator;
private $responseFactory;
public function __construct(ResourceGenerator $resourceGenerator, HalResponseFactory $responseFactory)
{
$this->resourceGenerator = $resourceGenerator;
$this->responseFactory = $responseFactory;
}
public function handle(ServerRequestInterface $request) : ResponseInterface
{
// do some work ...
$resource = $this->resourceGenerator->fromObject($book, $request);
return $this->responseFactory->createResponse($request, $book);
}
}
```
This handler needs a HAL resource generator. More specifically, it needs the one
specific to our module. As such, we'll define our factory as follows:

```php
// in src/Api/CreateBookHandlerFactory.php:
namespace Api;

use Psr\Container\ContainerInterface;
use Zend\Expressive\Hal\HalResponseFactory;

class CreateBookHandlerFactory
{
public function __invoke(ContainerInterface $container) : CreateBookHandler
{
return new CreateBookHandler(
ResourceGenerator::class, // module-specific service name!
HalResponseFactory::class
);
}
}
```

You can create any number of such handlers for your module; the above
demonstrates how and where injection of the alternate resource generator occurs.

## Creating our pipeline and routes

Now we can create our pipeline and routes.

Generally when piping to an application instance, we can specify a class name of
middleware to pipe, or an array of middleware:

```php
// in config/pipeline.php:
$app->pipe('/api', [
\Zend\ProblemDetails\ProblemDetailsMiddleware::class,
\Api\RouteMiddleware::class, // module-specific routing middleware!
ImplicitHeadMiddleware::class,
ImplicitOptionsMiddleware::class,
MethodNotAllowedMiddleware::class,
\Api\UrlHelperMiddleware::class, // module-specific URL helper middleware!
DispatchMiddleware::class,
\Zend\ProblemDetails\ProblemDetailsNotFoundHandler::class,
]);
```

However, we have both the pipeline _and_ routes, and we likely want to indicate
the exact behavior of this pipeline. Additionally, we may want to re-use this
pipeline in other applications; pushing this into the application configuration
makes that more error-prone.

As such, we will create a factory that generates and returns a
`Zend\Stratigility\MiddlewarePipe` instance that is fully configured for our
module. As part of this functionality, we will also add our module-specific
routing.

```php
// In src/Api/PipelineFactory.php:
namespace Api;

use Psr\Container\ContainerInterface;
use Zend\Expressive\MiddlewareFactory;
use Zend\Expressive\Router\Middleware as RouterMiddleware;
use Zend\ProblemDetails\ProblemDetailsMiddleware;
use Zend\ProblemDetails\ProblemDetailsNotFoundHandler;
use Zend\Stratigility\MiddlewarePipe;

class PipelineFactory
{
public function __invoke(ContainerInterface $container) : MiddlewarePipe
{
$factory = $container->get(MiddlewareFactory::class);

// First, create our middleware pipeline
$pipeline = new MiddlewarePipe();
$pipeline->pipe($factory->lazy(ProblemDetailsMiddleware::class));
$pipeline->pipe($factory->lazy(RouteMiddleware::class)); // module-specific!
$pipeline->pipe($factory->lazy(RouterMiddleware\ImplicitHeadMiddleware::class));
$pipeline->pipe($factory->lazy(RouterMiddleware\ImplicitOptionsMiddleware::class));
$pipeline->pipe($factory->lazy(RouterMiddleware\MethodNotAllowedMiddleware::class));
$pipeline->pipe($factory->lazy(UrlHelperMiddlweare::class)); // module-specific!
$pipeline->pipe($factory->lazy(RouterMiddleware\DispatchMiddleware::class));
$pipeline->pipe($factory->lazy(ProblemDetailsNotFoundHandler::class));

// Second, we'll create our routes
$router = $container->get(Router::class); // Retrieve our module-specific router
$routes = new RouteCollector($router); // Create a route collector to simplify routing

// Start routing:
$routes->post('/books', $factory->lazy(CreateBookHandler::class));

// Return the pipeline now that we're done!
return $pipeline;
}
}
```

Note that the routing definitions do **not** include the prefix `/api`; this is
because that prefix will be stripped when we path-segregate our API middleware
pipeline. All routing will be _relative_ to that path.

## Creating a path-segregated pipeline

Finally, we will attach our pipeline to the application, using path segregation:

```php
// in config/pipeline.php:
$app->pipe('/api', \Api\Pipeline::class);
```

This statement tells the application to pipe the pipeline returned by our
`PipelineFactory` under the path `/api`; that path will be stripped from
requests when passed to the underlying middleware.

At this point, we now have a re-usable module, complete with its own routing,
with URI generation that will include the base path under which we have
segregated the pipeline!
23 changes: 22 additions & 1 deletion docs/book/factories.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ configured instances for your use.
- Registered as service: `Zend\Expressive\Hal\LinkGenerator`
- Generates instance of: `Zend\Expressive\Hal\LinkGenerator`
- Depends on:
- `Zend\Expressive\Hal\LinkGenerator\UrlGenerator` service
- `Zend\Expressive\Hal\LinkGenerator\UrlGeneratorInterface` service

Since version 1.1.0, this factory allows an optional constructor argument,
`$urlGeneratorServiceName`. It defaults to
`Zend\Expressive\Hal\LinkGenerator\UrlGeneratorInterface`,
but you may specify an alternate service if desired. This may be useful, for
instance, when using an alternate router in a path-segregated middleware
pipeline, which would necessitate a different `UrlHelper` instance, and an
alternate URL generator that consumes it.

## Zend\Expressive\Hal\LinkGenerator\ExpressiveUrlGeneratorFactory

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

Since version 1.1.0, this factory allows an optional constructor argument, `$urlHelperServiceName`.
It defaults to `Zend\Expressive\Helper\UrlHelper`, but you may specify an
alternate service if desired. This may be useful, for instance, when using an
alternate router in a path-segregated middleware pipeline, which would
necessitate a different `UrlHelper` instance.

## Zend\Expressive\Hal\LinkGenerator\UrlGeneratorInterface

- Registered as service: `Zend\Expressive\Hal\LinkGenerator\UrlGeneratorInterface`
Expand Down Expand Up @@ -142,3 +156,10 @@ the namespace.
If you wish to use a container implementation other than the
`Zend\Hydrator\HydratorPluginManager`, either register it under that service
name, or create an alternate factory.

Since version 1.1.0, this factory allows an optional constructor argument, `$linkGeneratorServiceName`.
It defaults to `Zend\Expressive\Hal\LinkGenerator`, but you may specify an
alternate service if desired. This may be useful, for instance, when using an
alternate router in a path-segregated middleware pipeline, which would
necessitate a different `UrlHelper` instance, an alternate URL generator that
consumes it, and an alternate `LinkGenerator` consuming the URL generator.
3 changes: 2 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ pages:
- "Factories": factories.md
- Cookbook:
- "Generating Custom Links In Middleware and Request Handlers": cookbook/generating-custom-links-in-middleware.md
- "Using the ResourceGenerator in path-segregated middleware": cookbook/path-segregated-uri-generation.md
site_name: Hypertext Application Language
site_description: 'Hypertext Application Language for PSR-7 Applications'
repo_url: 'https://github.com/zendframework/zend-expressive-hal'
copyright: 'Copyright (c) 2017 <a href="http://www.zend.com/">Zend Technologies USA Inc.</a>'
copyright: 'Copyright (c) 2017-2018 <a href="http://www.zend.com/">Zend Technologies USA Inc.</a>'
Loading

0 comments on commit 33c7906

Please sign in to comment.