Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
* develop: (28 commits)
  specify next release
  add documentation to decorate the env and os
  add Optional middleware documentation
  mention it ships with middlewares
  typo
  add documentation
  move where Environment is built for test uniformity
  remove internal flag as it can be called in users tests
  fix readme header
  fix calling configure methods
  declare app configuration as mutation free
  mention user facing classes/interfaces
  flag Application::run as internal
  add shortcut to use a service as a route handler
  allow to handle the 404 response
  allow to map request handlers
  add router
  add http application
  fix class name
  use innmind/di 2
  ...
  • Loading branch information
Baptouuuu committed Jan 1, 2023
2 parents 8144187 + 00d9289 commit fe40a96
Show file tree
Hide file tree
Showing 31 changed files with 3,185 additions and 4 deletions.
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# Changelog

## [Unreleased]
## 1.0.0 - 2023-01-01

### Added

- `Innmind\Framework\Application`
- `Innmind\Framework\Main\Cli`
- `Innmind\Framework\Main\Http`
- `Innmind\Framework\Middleware`
- `Innmind\Framework\Middleware\Optional`
- `Innmind\Framework\Middleware\LoadDotEnv`
- `Innmind\Framework\Environment`
- `Innmind\Framework\Http\RequestHandler`
123 changes: 121 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# Filesystem
# Framework

[![Build Status](https://github.com/Innmind/Framework/workflows/CI/badge.svg?branch=main)](https://github.com/Innmind/Framework/actions?query=workflow%3ACI)
[![codecov](https://codecov.io/gh/Innmind/Framework/branch/develop/graph/badge.svg)](https://codecov.io/gh/Innmind/Framework)
[![Type Coverage](https://shepherd.dev/github/Innmind/Framework/coverage.svg)](https://shepherd.dev/github/Innmind/Framework)

Minimalist HTTP/CLI framework that accomodate to simple applications to complex ones via middlewares.

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)

## Installation

```sh
Expand All @@ -12,4 +18,117 @@ composer require innmind/framework

## Usage

TODO
Take a look at the [documentation](docs/) for a more in-depth understanding of the framework.

### Http

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.

```php
<?php
declare(strict_types = 1);

require 'path/to/composer/autoload.php';

use Innmind\Framework\{
Main\Http,
Application,
Http\Routes,
};
use Innmind\Router\{
Route,
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->appendRoutes(
static fn(Routes $routes) => $routes
->add(Route::literal('GET /')->handle(
static fn(ServerRequest $request) => new Response(
StatusCode::ok,
$request->protocolVersion(),
null,
Content\Lines::ofContent('Hello world!'),
),
))
->add(Route::literal('GET /{name}')->handle(
static fn(ServerRequest $request, Variables $variables) => new Response(
StatusCode::ok,
$request->protocolVersion(),
null,
Content\Lines::ofContent("Hello {$variables->get('name')}!"),
),
)),
);
}
};
```

You can run this script via `cd public && php -S localhost:8080`. If you open your web browser it will display `Hello world!` and if you go to `/John` it will display `Hello John!`.

### Cli

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`.

```php
<?php
declare(strict_types = 1);

require 'path/to/composer/autoload.php';

use Innmind\Framework\{
Main\Cli,
Application,
};
use Innmind\OperatingSystem\OperatingSystem;
use Innmind\TimeContinuum\{
Clock,
Earth\Format\ISO8601,
};
use Innmind\DI\Container;
use Innmind\CLI\{
Console,
Command,
};
use Innmind\Immutable\Str;

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';
}
},
);
}
};
```

We can execute our script via `php filename.php` (or `php filename.php today`) and it would output something like `We are the: 2022-12-30T14:04:50+00:00`.
14 changes: 13 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,16 @@
"issues": "http://github.com/Innmind/framework/issues"
},
"require": {
"php": "~8.1"
"php": "~8.1",
"innmind/operating-system": "^3.2",
"innmind/cli": "^3.1",
"innmind/immutable": "^4.9",
"innmind/di": "^2.0",
"ramsey/uuid": "^4.7",
"innmind/url": "^4.1",
"innmind/filesystem": "^6.0",
"innmind/http-server": "^3.0",
"innmind/router": "^3.0"
},
"autoload": {
"psr-4": {
Expand All @@ -38,5 +47,8 @@
},
"suggest": {
"innmind/black-box": "For property based testing"
},
"provide": {
"innmind/framework-middlewares": "1.0"
}
}
15 changes: 15 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Framework

The philosophy behind this framework is to have a minimalist foundation to be able to build simple apps but can also accomodate for more complex applications through composition (of the configuration, commands, request handlers and more).

Another important design is to expose to you the input to handle and an abstraction of the operating system it runs on so you only need to focus on WHAT your app needs to do and NOT HOW.

These topics will guide you through the simplest cases to more complex ones:
- [Build an HTTP app](http.md)
- [Build a CLI app](cli.md)
- [Services](services.md)
- [Middlewares](middlewares.md)
- [Build an app that runs through HTTP and CLI](http-and-cli.md)
- [Testing](testing.md)
- [Add variables to the environment](environment.md)
- [Decorate the operating system](operating-system.md)
158 changes: 158 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Build a CLI app

The first of any CLI app is to create an `entrypoint.php` that you'll call with the `php` command.

```php
<?php
declare(strict_types = 1);

require 'path/to/composer/autoload.php';

use Innmind\Framework\{
Main\Cli,
Application,
};

new class extends Cli {
protected function configure(Application $app): Application
{
return $app;
}
};
```

By default this application will write `Hello world` when you call `php entrypoint.php`.

## Handle commands

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

```php
use Innmind\Framework\{
Main\Cli,
Application,
};
use Innmind\CLI\{
Console,
Command,
};
use Innmind\DI\Container;
use Innmind\AMQP\{
Client,
Command\Publish,
Command\Get,
Model\Basic\Message,
};
use Innmind\Immutable\Str;

new class extends Cli {
protected function configure(Application $app): Application
{
return $app
->service('producer-client', /* see services topic */)
->service('consumer-client', /* see services topic */)
->command(static fn(Container $container) => new class($container('producer-client')) implements Command {
public function __construct(
private Client $amqp,
) {
}

public function __invoke(Console $console): Console
{
$message = Message::of(Str::of(
$console->arguments()->get('url'),
));

return $this
->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")),
);
}

public function usage(): string
{
return 'publish url';
}
})
->command(static fn(Container $container) => new class($container('consumer-client')) implements Command {
public function __construct(
private Client $amqp,
) {
}

public function __invoke(Console $console): Console
{
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")),
);
}

public function usage(): string
{
return 'consume';
}
});
}
};
```

This example creates 2 commands `publish` (that expect one argument) and `consume`. Each command relies on a service to access the AMQP client.

You can call `php entrypoint.php publish https://github.com` that will call the first command and `php entrypoint.php consume` will call the second one.

## Execute code on any command

Sometimes you want to execute some code on every command. So far your only approach would be to use inheritance on each `Command` but this leads to bloated code.

Fortunately there is better approach: composition of `Command`s.

```php
use Innmind\Framework\{
Main\Cli,
Application,
};
use Innmind\CLI\{
Console,
Command,
};

new class extends Cli {
protected function configure(Application $app): Application
{
return $app
->mapCommand(
static fn(Command $command) => new class($command) implements Command {
public function __construct(
private Command $inner,
) {
}

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

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

public function usage(): string
{
return $this->inner->usage();
}
}
)
->service(/* ... */)
->service(/* ... */)
->command(/* ... */)
->command(/* ... */);
}
};
```
Loading

0 comments on commit fe40a96

Please sign in to comment.