Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
970 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.php). | ||
|
||
```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(/* ... */); | ||
} | ||
}; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# 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)) 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, | ||
}; | ||
use Innmind\OperatingSystem\OperatingSystem; | ||
use Innmind\DI\Container; | ||
use Innmind\Router\Route; | ||
use Innmind\Url\Path; | ||
|
||
final class Kernel implements Middleware | ||
{ | ||
public function __invoke(Application $app): Application | ||
{ | ||
return $app | ||
->service( | ||
'images', | ||
static fn($_, OperatingSystem $os) => $os->filesystem()->mount(Path::of('somewhere/on/the/filesystem/')), | ||
) | ||
->service('amqp', /* see services topic */) | ||
->service('upload', static fn(Container $container) => new UploadHandler( // imaginary class | ||
$container('images'), | ||
$container('amqp'), | ||
)) | ||
->appendRoutes( | ||
static fn(Routes $routes, Container $container) => $routes->add( | ||
Route::literal('POST /upload')->handle(Service::of($container, 'upload')), | ||
), | ||
) | ||
->command(static fn(Container $container) => new ThumbnailWorker( // imaginary class | ||
$container('images'), | ||
$container('amqp'), | ||
)); | ||
} | ||
} | ||
``` | ||
|
||
Then you can use this middleware like this: | ||
|
||
```php | ||
use Innmind\Framework\{ | ||
Main\Cli, | ||
Application, | ||
}; | ||
|
||
new class extends Cli { | ||
protected function configure(Application $app): Application | ||
{ | ||
return $app->map(new Kernel); | ||
} | ||
} | ||
``` | ||
|
||
Or like this: | ||
|
||
```php | ||
use Innmind\Framework\{ | ||
Main\Http, | ||
Application, | ||
}; | ||
|
||
new class extends Http { | ||
protected function configure(Application $app): Application | ||
{ | ||
return $app->map(new Kernel); | ||
} | ||
} | ||
``` | ||
|
||
In the case on the CLI the call to `appendRoutes` will have no effect and for HTTP `command` will have no effect. |
Oops, something went wrong.