Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
* develop:
  specify next release
  mention the async server is not meant to be used in production
  highlight notes
  add shortcut to reference a service to handle a route
  add short syntax to declare routes
  • Loading branch information
Baptouuuu committed May 1, 2023
2 parents 917dfe0 + 766d354 commit 5205bd8
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 12 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 1.3.0 - 2023-05-01

### Added

- `Innmind\Framework\Application::route()`
- `Innmind\Framework\Http\To`

## 1.2.0 - 2023-04-29

### Added
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Take a look at the [documentation](docs/) for a more in-depth understanding of t

The first step is to create the index file that will be exposed via a webserver (for example `public/index.php`). Then you need to specify the routes you want to handle.

**Note**: if you don't configure any route it will respond with `404 Not Found` with an empty body.
> **Note** if you don't configure any route it will respond with `404 Not Found` with an empty body.
```php
<?php
Expand Down Expand Up @@ -80,7 +80,7 @@ You can run this script via `cd public && php -S localhost:8080`. If you open yo

The entrypoint of your cli tools will look something like this.

**Note**: by default if you don't configure any command it will always display `Hello world`.
> **Note** by default if you don't configure any command it will always display `Hello world`.
```php
<?php
Expand Down
6 changes: 4 additions & 2 deletions docs/experimental/async-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The framework comes with an HTTP server entirely built in PHP allowing you to serve your app without extra dependencies in ther earlist stages of your project.

**Note**: This feature is optional, to use it you must before run `composer require innmind/async-http-server`.
> **Note** This feature is optional, to use it you must before run `composer require innmind/async-http-server`.
To use it is similar to the standard [http](../http.md) handler, the first difference is the namespace of the main entrypoint:

Expand All @@ -29,4 +29,6 @@ Note the namespace is `Main\Async\Http` instead of `Main\Http`. The other differ

All the configuration of the `Application` object is identical to the other contexts.

**Note**: The server currently does have limitations, streamed requests (via `Transfer-Encoding`) are not supported and multipart requests are not parsed.
> **Note** The server currently does have limitations, streamed requests (via `Transfer-Encoding`) are not supported and multipart requests are not parsed.
> **Warning** This server was built to showcase in a conference talk the ability to switch between synchronous code and asynchronous code without changing the app code. Do **NOT** use this server in production.
48 changes: 43 additions & 5 deletions docs/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,43 @@ new class extends Http {

For simple apps having the whole behaviour next to the route can be ok. But like in this case it can be repetitive, for such case we can specify our behaviours elsewhere: [services](#Services).

## Short syntax

The previous shows the default way to declare routes, but for very simple apps it can be a bit verbose. The framework provides a shorter syntax to handle routes:

```php
use Innmind\Framework\{
Main\Http,
Application,
};
use Innmind\Router\Route\Variables;
use Innmind\Http\Message\{
ServerRequest,
Response\Response,
StatusCode,
};
use Innmind\Filesystem\File\Content;

new class extends Http {
protected function configure(Application $app): Application
{
return $app
->route('GET /', static fn(ServerRequest $request) => new Response(
StatusCode::ok,
$request->protocolVersion(),
null,
Content\Lines::ofContent('Hello world!'),
))
->route('GET /{name}', static fn(ServerRequest $request, Variables $variables) => new Response(
StatusCode::ok,
$request->protocolVersion(),
null,
Content\Lines::ofContent("Hello {$variables->get('name')}!"),
));
}
};
```

## Services

Services are any object that are referenced by a string in a [`Container`](https://github.com/Innmind/DI). For example let's take the route handler from the previous section and move them inside services.
Expand All @@ -104,6 +141,7 @@ use Innmind\Framework\{
Application,
Http\Routes,
Http\Service,
Http\To,
};
use Innmind\DI\Container;
use Innmind\Router\{
Expand Down Expand Up @@ -151,16 +189,16 @@ new class extends Http {
)
->appendRoutes(
static fn(Routes $routes, Container $container) => $routes
->add(Route::literal('GET /')->handle(Service::of($container, 'hello-word')))
->add(Route::literal('GET /{name}')->handle(Service::of($container, 'hello-name'))),
);
->add(Route::literal('GET /')->handle(Service::of($container, 'hello-word'))),
)
->route('GET /{name}', To::service('hello-name'));
}
};
```

Here the services are invokable anonymous classes to conform to the callable expected for a `Route` but you can create dedicated classes for each one.

**Note**: Head to the [services topic](services.md) for a more in-depth look of what's possible.
> **Note** Head to the [services topic](services.md) for a more in-depth look of what's possible.
## Executing code on any route

Expand Down Expand Up @@ -216,7 +254,7 @@ This example will refuse any request that doesn't have an `Authorization` header

You can have multiple calls to `mapRequestHandler` to compose behaviours like an onion.

**Note**: the default request handler is the inner router of the framework, this means that you can completely change the default behaviour of the framework by returning a new request handler that never uses the default one.
> **Note** the default request handler is the inner router of the framework, this means that you can completely change the default behaviour of the framework by returning a new request handler that never uses the default one.
## Handling unknown routes

Expand Down
2 changes: 1 addition & 1 deletion docs/middlewares.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Middlewares are a way to regroup all the configuration you've seen in other topics under a name. This means that you can either group part of your own application undeer a middleware or expose a package for other to use via Packagist.

**Note**: you can search for [`innmind/framework-middlewares` on Packagist](https://packagist.org/providers/innmind/framework-middlewares) for middlewares published by others.
> **Note** you can search for [`innmind/framework-middlewares` on Packagist](https://packagist.org/providers/innmind/framework-middlewares) for middlewares published by others.
Let's say you have an application that sends emails you could have a middleware that looks like this:

Expand Down
4 changes: 2 additions & 2 deletions docs/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

For both [HTTP](http.md) and [CLI](cli.md) applications a service is an object referenced by a name in a [`Container`](https://github.com/Innmind/DI).

**Note**: since a container only deals with objects Psalm will complain of type mismatches, so you'll have to suppress those errors (for now).
> **Note** since a container only deals with objects Psalm will complain of type mismatches, so you'll have to suppress those errors (for now).
## Defining a service

Expand Down Expand Up @@ -35,7 +35,7 @@ new class extends Http|Cli {

This example defines a single service named `amqp-client` that relies on the `OperatingSystem` in order to work.

**Note**: this example uses [`innmind/amqp`](https://github.com/innmind/amqp)
> **Note** this example uses [`innmind/amqp`](https://github.com/innmind/amqp)
## Configure via environment variables

Expand Down
22 changes: 22 additions & 0 deletions src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
ServerRequest,
Response,
};
use Innmind\Router\{
Route,
Route\Variables,
};

final class Application
{
Expand Down Expand Up @@ -123,6 +127,24 @@ public function mapCommand(callable $map): self
return $this;
}

/**
* @psalm-mutation-free
*
* @param literal-string $pattern
* @param callable(ServerRequest, Variables, Container, OperatingSystem, Environment): Response $handle
*/
public function route(string $pattern, callable $handle): self
{
if (
$this->app instanceof Application\Http ||
$this->app instanceof Application\Async\Http
) {
return new self($this->app->route($pattern, $handle));
}

return $this;
}

/**
* @psalm-mutation-free
*
Expand Down
25 changes: 25 additions & 0 deletions src/Application/Async/Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
ServerRequest,
Response,
};
use Innmind\Router\{
Route,
Route\Variables,
};
use Innmind\Immutable\Maybe;

/**
Expand Down Expand Up @@ -153,6 +157,27 @@ public function service(string $name, callable $definition): self
);
}

/**
* @psalm-mutation-free
*
* @param literal-string $pattern
* @param callable(ServerRequest, Variables, Container, OperatingSystem, Environment): Response $handle
*/
public function route(string $pattern, callable $handle): self
{
return $this->appendRoutes(
static fn($routes, $container, $os, $env) => $routes->add(
Route::literal($pattern)->handle(static fn($request, $variables) => $handle(
$request,
$variables,
$container,
$os,
$env,
)),
),
);
}

/**
* @psalm-mutation-free
*
Expand Down
25 changes: 25 additions & 0 deletions src/Application/Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
ServerRequest,
Response,
};
use Innmind\Router\{
Route,
Route\Variables,
};
use Innmind\Immutable\Maybe;

final class Http
Expand Down Expand Up @@ -132,6 +136,27 @@ public function service(string $name, callable $definition): self
);
}

/**
* @psalm-mutation-free
*
* @param literal-string $pattern
* @param callable(ServerRequest, Variables, Container, OperatingSystem, Environment): Response $handle
*/
public function route(string $pattern, callable $handle): self
{
return $this->appendRoutes(
static fn($routes, $container, $os, $env) => $routes->add(
Route::literal($pattern)->handle(static fn($request, $variables) => $handle(
$request,
$variables,
$container,
$os,
$env,
)),
),
);
}

/**
* @psalm-mutation-free
*
Expand Down
38 changes: 38 additions & 0 deletions src/Http/To.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
declare(strict_types = 1);

namespace Innmind\Framework\Http;

use Innmind\Http\Message\{
ServerRequest,
Response,
};
use Innmind\DI\Container;
use Innmind\Router\Route\Variables;

final class To
{
private string $service;

private function __construct(string $service)
{
$this->service = $service;
}

public function __invoke(
ServerRequest $request,
Variables $variables,
Container $container,
): Response {
/**
* @psalm-suppress InvalidFunctionCall If it fails here then the service doesn't conform to the signature callable(ServerRequest, Variables): Response
* @var Response
*/
return $container($this->service)($request, $variables);
}

public static function service(string $service): self
{
return new self($service);
}
}
Loading

0 comments on commit 5205bd8

Please sign in to comment.