Skip to content
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
89 changes: 58 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ Minimalist HTTP/CLI framework that accomodate to simple applications to complex

The framework configuration is immutable and use a declarative approach.

**Important**: to correctly use this library you must validate your code with [`vimeo/psalm`](https://packagist.org/packages/vimeo/psalm)
> [!IMPORTANT]
> to correctly use this library you must validate your code with [`vimeo/psalm`](https://packagist.org/packages/vimeo/psalm)

## Installation

Expand Down Expand Up @@ -36,30 +37,45 @@ require 'path/to/composer/autoload.php';
use Innmind\Framework\{
Main\Http,
Application,
Http\Route,
};
use Innmind\Router\Route\Variables;
use Innmind\DI\Service;
use Innmind\Http\{
ServerRequest,
Response,
Response\StatusCode,
};
use Innmind\Filesystem\File\Content;
use Innmind\Immutable\Attempt;

enum Services implements Service
{
case hello;
}

new class extends Http {
protected function configure(Application $app): Application
{
return $app
->route('GET /', static fn(ServerRequest $request) => Response::of(
->service(Services::hello, static fn() => static fn(
ServerRequest $request,
?string $name = null,
) => Attempt::result(Response::of(
StatusCode::ok,
$request->protocolVersion(),
null,
Content::ofString('Hello world!'),
Content::ofString(\sprintf(
'Hello %s!',
$name ?? 'world',
)),
)))
->route(Route::get(
'/',
Services::hello,
))
->route('GET /{name}', static fn(ServerRequest $request, Variables $variables) => Response::of(
StatusCode::ok,
$request->protocolVersion(),
null,
Content::ofString("Hello {$variables->get('name')}!"),
->route(Route::get(
'/{name}',
Services::hello,
));
}
};
Expand Down Expand Up @@ -87,38 +103,49 @@ use Innmind\Framework\{
use Innmind\OperatingSystem\OperatingSystem;
use Innmind\TimeContinuum\{
Clock,
Earth\Format\ISO8601,
Format,
};
use Innmind\DI\Container;
use Innmind\CLI\{
Console,
Command,
Command\Usage,
};
use Innmind\Immutable\Str;
use Innmind\Immutable\{
Attempt,
Str,
};

enum Services implements Service
{
case clock;
}

new class extends Cli {
protected function configure(Application $app): Application
{
return $app->command(
static fn(Container $container, OperatingSystem $os) => new class($os->clock()) implements Command {
public function __construct(
private Clock $clock,
) {
}

public function __invoke(Console $console): Console
{
$today = $this->clock->now()->format(new ISO8601);

return $console->output(Str::of("We are the: $today\n"));
}

public function usage(): string
{
return 'today';
}
},
);
return $app
->service(Services::clock, static fn($_, OperatingSystem $os) => $os->clock())
->command(
static fn(Container $container) => new class($container(Services::clock)) implements Command {
public function __construct(
private Clock $clock,
) {
}

public function __invoke(Console $console): Attempt
{
$today = $this->clock->now()->format(Format::iso8601());

return $console->output(Str::of("We are the: $today\n"));
}

public function usage(): Usage
{
return Usage::of('today');
}
},
);
}
};
```
Expand Down
58 changes: 30 additions & 28 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ By default this application will write `Hello world` when you call `php entrypoi

## Handle commands

This example reuses the AMQP clients defined in the [services topic](services.md).
This example reuses the AMQP clients defined in the [services section](services.md).

```php
use Innmind\Framework\{
Expand All @@ -35,6 +35,7 @@ use Innmind\Framework\{
use Innmind\CLI\{
Console,
Command,
Command\Usage,
};
use Innmind\DI\{
Container,
Expand All @@ -46,7 +47,10 @@ use Innmind\AMQP\{
Command\Get,
Model\Basic\Message,
};
use Innmind\Immutable\Str;
use Innmind\Immutable\{
Attempt,
Str,
};

enum Services implements Service
{
Expand All @@ -58,15 +62,15 @@ new class extends Cli {
protected function configure(Application $app): Application
{
return $app
->service(Services::producerClient, /* see services topic */)
->service(Services::consumerClient, /* see services topic */)
->service(Services::producerClient, /* see services section */)
->service(Services::consumerClient, /* see services section */)
->command(static fn(Container $container) => new class($container(Services::producerClient)) implements Command {
public function __construct(
private Client $amqp,
) {
}

public function __invoke(Console $console): Console
public function __invoke(Console $console): Attempt
{
$message = Message::of(Str::of(
$console->arguments()->get('url'),
Expand All @@ -76,19 +80,17 @@ new class extends Cli {
->client
->with(Publish::one($message)->to('some-exchange'))
->run($console)
->match(
static fn($console) => $console->output(
Str::of("Message published\n"),
),
static fn() => $console->error(
Str::of("Something went wrong\n"),
),
);
->flatMap(static fn($console) => $console->output(
Str::of("Message published\n"),
))
->recover(static fn() => $console->error(
Str::of("Something went wrong\n"),
));
}

public function usage(): string
public function usage(): Usage
{
return 'publish url';
return Usage::of('publish')->argument('url');
}
})
->command(static fn(Container $container) => new class($container(Services::consumerClient)) implements Command {
Expand All @@ -97,25 +99,23 @@ new class extends Cli {
) {
}

public function __invoke(Console $console): Console
public function __invoke(Console $console): Attempt
{
return $this
->client
->with(Get::of('some-queue'))
->run($console)
->match(
static fn($console) => $console->output(
Str::of("One message pulled from queue\n"),
),
static fn() => $console->error(
Str::of("Something went wrong\n"),
),
);
->flatMap(static fn($console) => $console->output(
Str::of("One message pulled from queue\n"),
))
->recover(static fn() => $console->error(
Str::of("Something went wrong\n"),
));
}

public function usage(): string
public function usage(): Usage
{
return 'consume';
return Usage::of('consume');
}
});
}
Expand All @@ -140,7 +140,9 @@ use Innmind\Framework\{
use Innmind\CLI\{
Console,
Command,
Command\Usage,
};
use Innmind\Immutable\Attempt;

new class extends Cli {
protected function configure(Application $app): Application
Expand All @@ -153,14 +155,14 @@ new class extends Cli {
) {
}

public function __invoke(Console $console): Console
public function __invoke(Console $console): Attempt
{
// do something before the real command

return ($this->inner)($console);
}

public function usage(): string
public function usage(): Usage
{
return $this->inner->usage();
}
Expand Down
2 changes: 1 addition & 1 deletion docs/experimental/async-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The framework comes with an HTTP server entirely built in PHP allowing you to se

To use it is similar to the standard [http](../http.md) handler, the first difference is the namespace of the main entrypoint:

```php
```php title="index.php" hl_lines="7"
<?php
declare(strict_types = 1);

Expand Down
22 changes: 10 additions & 12 deletions docs/http-and-cli.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
# Build an app that runs through HTTP and CLI

If you looked at how to build an [HTTP](http.md) and [CLI](cli.md) app you may have noticed that we always configure the same `Application` class. This is intentional to allow you to configure services once (in a [middleware](middlewares.md)) and use them in both contexts.
If you looked at how to build an [HTTP](http.md) and [CLI](cli.md) apps you may have noticed that we always configure the same `Application` class. This is intentional to allow you to configure services once (in a [middleware](middlewares.md)) and use them in both contexts.

Let's take an imaginary app where you can upload images via HTTP (persists them to the filesystem) and a CLI command that pulls a message from an AMQP queue to build the thumbnail. We would build a middleware that roughly looks like this:

```php
use Innmind\Framework\{
Application,
Middleware,
Http\Routes,
Http\Service,
Http\Route,
};
use Innmind\OperatingSystem\OperatingSystem;
use Innmind\DI\{
Container,
Service,
};
use Innmind\Router\Route;
use Innmind\Url\Path;

enum Services implements Service
Expand All @@ -35,18 +33,18 @@ final class Kernel implements Middleware
Services::images,
static fn($_, OperatingSystem $os) => $os
->filesystem()
->mount(Path::of('somewhere/on/the/filesystem/')),
->mount(Path::of('somewhere/on/the/filesystem/'))
->unwrap(),
)
->service(Services::amqp, /* see services topic */)
->service(Services::amqp, /* see services section */)
->service(Services::upload, static fn(Container $container) => new UploadHandler( //(1)
$container(Services::images),
$container(Services::amqp),
))
->appendRoutes(
static fn(Routes $routes, Container $container) => $routes->add(
Route::literal('POST /upload')->handle(Service::of($container, Services::upload)),
),
)
->route(Route::post(
'/upload',
Services::upload,
))
->command(static fn(Container $container) => new ThumbnailWorker( //(2)
$container(Services::images),
$container(Services::amqp),
Expand Down Expand Up @@ -90,4 +88,4 @@ new class extends Http {
}
```

In the case on the CLI the call to `appendRoutes` will have no effect and for HTTP `command` will have no effect.
In the case on the CLI the call to `route` will have no effect and for HTTP `command` will have no effect.
Loading