Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 44 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

*Begin developing PSR-7 middleware applications in minutes!*

**Note: This project is a work in progress. Don't use it in production!**

zend-expressive builds on [zend-stratigility](https://github.com/zendframework/zend-stratigility)
to provide a minimalist PSR-7 middleware framework for PHP, with the following
features:
Expand All @@ -23,17 +21,50 @@ features:

## Installation

Install this library using composer:
We provide two ways to install Expressive, both using
[Composer](https://getcomposer.org): via our
[skeleton project and installer](https://github.com/zendframework/zend-expressive-skeleton],
or manually.

### Using the skeleton + installer

The simplest way to install and get started is using the skeleton project, which
includes installer scripts for choosing a router, dependency injection
container, and optionally a template renderer and/or error handler. The skeleton
also provides configuration for officially supported dependencies.

To use the skeleton, use Composer's `create-project` command:

```bash
$ composer create-project zendframework/zend-expressive-skeleton <project dir>
```

This will prompt you through choosing your dependencies, and then create and
install the project in the `<project dir>` (omitting the `<project dir>` will
create and install in a `zend-expressive-skeleton/` directory).

### Manual Composer installation

You can install Expressive standalone using Composer:

```bash
$ composer require zendframework/zend-expressive
```

You will also need a router. We currently support:
However, at this point, Expressive is not usable, as you need to supply
minimally:

- [Aura.Router](https://github.com/auraphp/Aura.Router): `composer require aura/router`
- [FastRoute](https://github.com/nikic/FastRoute): `composer require nikic/fast-route`
- [ZF2 MVC Router](https://github.com/zendframework/zend-mvc): `composer require zendframework/zend-mvc`
- a router.
- a dependency injection container.

We currently support and provide the following routing integrations:

- [Aura.Router](https://github.com/auraphp/Aura.Router):
`composer require zendframework/zend-expressive-aurarouter`
- [FastRoute](https://github.com/nikic/FastRoute):
`composer require zendframework/zend-expressive-fastroute`
- [ZF2 MVC Router](https://github.com/zendframework/zend-mvc):
`composer require zendframework/zend-expressive-zendrouter`

We recommend using a dependency injection container, and typehint against
[container-interop](https://github.com/container-interop/container-interop). We
Expand All @@ -43,6 +74,12 @@ can recommend the following implementations:
`composer require zendframework/zend-servicemanager`
- [pimple-interop](https://github.com/moufmouf/pimple-interop):
`composer require mouf/pimple-interop`
- [Aura.Di](https://github.com/auraphp/Aura.Di):
`composer require aura/di:3.0.*@beta`

Additionally, you may optionally want to install a template renderer
implementation, and/or an error handling integration. These are covered in the
documentation.

## Documentation

Expand Down
268 changes: 268 additions & 0 deletions doc/book/quick-start-skeleton.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
# Quick Start: Using the Skeleton + Installer

The easiest way to get started with Expressive is to use the [skeleton
application and installer](https://github.com/zendframework/zend-expressive-skeleton).
The skeleton provides a generic structure for creating your applications, and
prompts you to choose a router, dependency injection container, template
renderer, and error handler from the outset.

## 1. Create a new project

First, we'll create a new project, using Composer's `create-project` command:

```bash
$ composer create-project zendframework/zend-expressive-skeleton expressive
```

This will prompt you to choose:

- A router. We recommend using the default, FastRoute.
- A dependency injection container. We recommend using the default, Zend
ServiceManager.
- A template renderer. You can ignore this when creating an API project, but if
you will be creating any HTML pages, we recommend installing one. We prefer
Plates.
- An error handler. Whoops is a very nice option for development, as it gives
you extensive, browseable information for exceptions and errors raised.

## 2. Start a web server

The Skeleton + Installer creates a full application structure that's ready-to-go
when complete. You can test it out using [built-in web
server](http://php.net/manual/en/features.commandline.webserver.php).

From the project root directory, execute the following:

```bash
$ php -S 0.0.0.0:8080 -t public/
```

This starts up a web server on localhost port 8080; browse to
http://localhost:8080/ to see if your application responds correctly!

## Next Steps

The skeleton makes the assumption that you will be writing your middleware as
classes, and using configuration to map routes to middleware. It also provides a
default structure for templates, if you choose to use them. Let's see how you
can create first vanilla middleware, and then templated middleware.

### Creating middleware

The easiest way to create middleware is to create a class that defines an
`__invoke()` method accepting a request, response, and callable "next" argument
(for invoking the "next" middleware in the queue). The skeleton defines an `App`
namespace for you, and suggests placing middleware under the namespace
`App\Action`.

Let's create a "Hello" action. Place the following in
`src/Action/HelloAction.php`:

```php
<?php
namespace App\Action;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class HelloAction
{
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
{
$query = $request->getQueryParams();
$target = isset($query['target']) ? $query['target'] : 'World';
$target = htmlspecialchars($target, ENT_HTML5, UTF-8);

$response->getBody()->write(sprintf(
'<h1>Hello, %s!</h1>',
$target
));
return $response->withHeader('Content-Type', 'text/html');
}
}
```

The above looks for a query string parameter "target", and uses its value to
provide a message, which is then returned in an HTML response.

Now we need to inform the application of this middleware, and indicate what
path will invoke it. Open the file `config/autoload/routes.global.php`. Inside
that file, you should have a structure similar to the following:

```php
return [
'dependencies' => [
/* ... */
],
'routes' => [
/* ... */
],
];
```

We're going to add an entry under `routes`:

```php
return [
/* ... */
'routes' => [
/* ... */
[
'name' => 'hello',
'path' => '/hello',
'middleware' => App\Action\HelloAction::class,
'allowed_methods' => ['GET'],
],
],
];
```

Once you've added the above entry, give it a try by going to each of the
following URIs:

- http://localhost:8080/hello
- http://localhost:8080/hello?target=ME

You should see the message change as you go between the two URIs!

### Using templates

You likely don't want to hardcode HTML into your middleware; so, let's use
templates. This particular exercise assumes you chose to use the
[Plates](http://platesphp.com) integration.

Templates are installed under the `templates/` subdirectory. By default, we also
register the template namespace `app` to correspond with the `templates/app`
subdirectory. Create the file `templates/app/hello-world.phtml` with the
following contents:

```php
<?php $this->layout('layout::default', ['title' => 'Greetings']) ?>

<h2>Hello, <?= $this->e($target) ?></h2>
```

Now that we have a template, we need to:

- Inject a renderer into our action class.
- Use the renderer to render the contents.

Replace your `src/Action/HelloAction.php` file with the following contents:

```php
<?php
namespace App\Action;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Expressive\Template\TemplateRendererInterface;

class HelloAction
{
private $renderer;

public function __construct(TemplateRendererInterface $renderer)
{
$this->renderer = $renderer;
}

public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
{
$query = $request->getQueryParams();
$target = isset($query['target']) ? $query['target'] : 'World';

return new HtmlResponse(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know creating a new HtmlResponse is very convenient, but I'm not sure if this is a good thing as we can lose previosuly added headers (CORS, etc).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm showing it as an example of how to create and return a custom response. Additionally, I'd argue injection of CORS headers might be something to intercept before emitting. We can worry about that later; this is a common pattern, and answers the questions of "why is populating a response so hard?!?!!?" which is why the custom response types exist.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense 👍

$this->renderer->render('app::hello-world', ['target' => $target])
);
}
}
```

The above modifies the class to accept a renderer to the constructor, and then
calls on it to render a template. A few things to note:

- We no longer need to escape our target; the template takes care of that for us.
- We're using a specific response type here, from
[Diactoros](https://github.com/zendframework/zend-diactoros), which is the
default PSR-7 implementation Expressive uses. This response type simplifies
our response creation.

How does the template renderer get into the action, however? The answer is
dependency injection.

For the next part of the example, we'll be creating and wiring a factory for
creating the `HelloAction` instance; the example assumes you used the default
selection for a dependency injection container, Zend ServiceManager.

Let's create a factory. Create the file `src/Action/HelloActionFactory.php` with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing note that we are assuming the developer picked Zend\ServiceManager.

the following contents:

```php
<?php
namespace App\Action;

use Interop\Container\ContainerInterface;
use Zend\Expressive\Template\TemplateRendererInterface;

class HelloActionFactory
{
public function __invoke(ContainerInterface $container)
{
return new HelloAction(
$container->get(TemplateRendererInterface::class)
);
}
}
```

With that in place, we'll now update our configuration. Open the file
`config/autoload/dependencies.global.php`; it should have a structure similar to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

routes.global.php

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. dependencies.global.php is correct here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the middleware factories should not be declared in routes.global.php?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah nvm, now I realized that the dependencies key in routes.global.php is meant to declare the router factory.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. They should be on the dependencies.global.php file.
On Oct 16, 2015 9:39 PM, "Daniel Gimenes" notifications@github.com wrote:

In doc/book/quick-start-skeleton.md
#158 (comment)
:

+use Interop\Container\ContainerInterface;
+use Zend\Expressive\Template\TemplateRendererInterface;
+
+class HelloActionFactory
+{

  • public function __invoke(ContainerInterface $container)
  • {
  •    return new HelloAction(
    
  •        $container->get(TemplateRendererInterface::class)
    
  •    );
    
  • }
    +}
    +```

+With that in place, we'll now update our configuration. Open the file
+config/autoload/dependencies.global.php; it should have a structure similar to

But the middleware factories should not be declared in routes.global.php?


Reply to this email directly or view it on GitHub
https://github.com/zendframework/zend-expressive/pull/158/files#r42304685
.

the following:

```php
return [
'dependencies' => [
'invokables' => [
/* ... */
],
'factories' => [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The factories key will not be there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is; I actually tried the code from this example. ;-)

When you use Zend ServiceManager, the dependencies.global.php file is populated as shown above, and actually has a factory for each of the HomePageAction and Zend\Expressive\Application. (It also defines an invokable pointing to the PingAction class.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why there is dependencies key in routes.global.php if we will config the middleware factories in dependencies.global.php?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nvm, I saw the dependencies key in routes.global.php and messed the whole thing :P

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't aware it was there until I started writing this; I'll be removing
it this weekend, as it's redundant, and not used.
On Oct 16, 2015 9:40 PM, "Daniel Gimenes" notifications@github.com wrote:

In doc/book/quick-start-skeleton.md
#158 (comment)
:

  •    );
    
  • }
    +}
    +`

+With that in place, we'll now update our configuration. Open the file
+config/autoload/dependencies.global.php; it should have a structure similar to
+the following:
+
+`php
+return [

  • 'dependencies' => [
  •    'invokables' => [
    
  •        /\* ... */
    
  •    ],
    
  •    'factories' => [
    

Why there is dependencies key in routes.global.php if we will config the
middleware factories in dependencies.global.php?


Reply to this email directly or view it on GitHub
https://github.com/zendframework/zend-expressive/pull/158/files#r42304712
.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, figured it all out. The dependencies key in the routes.global.php gets merged with the dependencies key in dependencies.global.php. It exists essentially just to keep the information together; you can tell at a glance in that file which router is used, and what factory is used to create it. While that information could be in dependencies.global.php, this is a nice solution as it keeps the information in one place.

/* ... */
],
],
];
```

We're going to tell our application that we have a _factory_ for our
`HelloAction` class:

```php
return [
'dependencies' => [
/* ... */
'factories' => [
/* ... */
App\Action\HelloAction::class => App\Action\HelloActionFactory::class,
],
],
];
```

Save that file, and now re-visit the URIs:

- http://localhost:8080/hello
- http://localhost:8080/hello?target=ME

Your page should now have the same layout as the landing page of the skeleton
application!

## Congratulations!

Congratulations! You've now created your application, and started writing
middleware! It's time to start learning about the rest of the features of
Expressive:

- [Containers](container/intro.md)
- [Routing](router/intro.md)
- [Templating](template/intro.md)
- [Error Handling](error-handling.md)
2 changes: 1 addition & 1 deletion doc/book/quick-start.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Quick Start
# Quick Start: Standalone Usage

Expressive allows you to get started at your own pace. You can start with
the simplest example, detailed below, or move on to a more structured,
Expand Down
3 changes: 2 additions & 1 deletion doc/bookdown.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"content": [
{"Intro": "../README.md"},
{"Overview and Features": "book/features.md"},
{"Quick Start": "book/quick-start.md"},
{"Quick Start: Skeleton + Installer": "book/quick-start-skeleton.md"},
{"Quick Start: Standalone": "book/quick-start.md"},
{"Applications": "book/application.md"},
"book/container/bookdown.json",
"book/router/bookdown.json",
Expand Down
3 changes: 2 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ site_dir: doc/html
pages:
- index.md
- { 'Overview and Features': features.md }
- { 'Quick Start': quick-start.md }
- { 'Quick Start: Skeleton + Installer': quick-start-skeleton.md }
- { 'Quick Start: Standalone': quick-start.md }
- { Applications: application.md }
- { Containers: [{ Introduction: container/intro.md }, { 'Container Factories': container/factories.md }, { 'Using zend-servicemanager': container/zend-servicemanager.md }, { 'Using Pimple': container/pimple.md }, { 'Using Aura.Di': container/aura-di.md }] }
- { 'Routing Adapters': [{ Introduction: router/intro.md }, { 'Routing Interface': router/interface.md }, { 'URI Generation': router/uri-generation.md }, { 'Routing vs Piping': router/piping.md }, { 'Using Aura': router/aura.md }, { 'Using FastRoute': router/fast-route.md }, { 'Using the ZF2 Router': router/zf2.md }] }
Expand Down