Skip to content

Commit 7aaafb8

Browse files
committed
fixes and docs
1 parent 1d9d1e5 commit 7aaafb8

File tree

5 files changed

+180
-41
lines changed

5 files changed

+180
-41
lines changed

README.md

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,110 @@
44
[![Tests](https://github.com/ensi-platform/laravel-initiator-propagation/actions/workflows/run-tests.yml/badge.svg?branch=master)](https://github.com/ensi-platform/laravel-initiator-propagation/actions/workflows/run-tests.yml)
55
[![Total Downloads](https://img.shields.io/packagist/dt/ensi/laravel-initiator-propagation.svg?style=flat-square)](https://packagist.org/packages/ensi/laravel-initiator-propagation)
66

7-
Generates Laravel application code from Open Api Specification files
7+
Provides a bunch of ready-to use middleware to integrate [ensi/initiator-propagation](https://github.com/ensi-platform/php-initiator-propagation/) in Laravel application.
8+
You are free to replace any of them with your own implementations.
89

910
## Installation
1011

1112
You can install the package via composer:
1213

1314
`composer require ensi/laravel-initiator-propagation`
1415

15-
You can publish config file like this:
16+
Publish config file like this:
1617

1718
`php artisan vendor:publish --provider="Ensi\LaravelInitiatorPropagation\LaravelInitiatorPropagationServiceProvider"`
1819

1920
## Basic Usage
2021

21-
## Setting initiator
22+
```php
23+
$holder = resolve(InitiatorHolder::class);
24+
var_dump($holder->getInitiator());
25+
$holder->setInitiator(new InitiatorDTO(...));
26+
```
2227

23-
You can use build-in `Ensi\InitiatorPropagation\ParseInitiatorHeaderLaravelMiddleware` to populate `InitiatorHolder` with data from incoming request.
24-
It's also recommended to add `$this->app->instance(InitiatorHolder::class, InitiatorHolder::getInstance());` to one of your service providers to make `InitiatorHolder` singleton injectable via Laravel Service Container
28+
### HTTP Requests
29+
30+
#### Setting initiator
31+
32+
You typically create a new initiator when you receive a HTTP request coming from a client you do not own. E.g in an API Gateway.
33+
There is a built-in `Ensi\LaravelInitiatorPropagation\SetInitiatorHttpMiddleware` for that.
34+
It creates an `InitiatorDTO` and places it to the `InitiatorHolder` singleton.
35+
- `userId` and `entrypoint` are set from request.
36+
- `app` is set according to config options.
37+
- `userType` is set from the package config. `userType` is empty for a not authenticated user.
38+
- `correlationId` and `startedAt` are set from request headers according to config options or generated from scratch.
39+
- `realUserId` and `realUserType` are left empty strings.
40+
41+
Be sure to add the midlleware AFTER Laravel middleware that sets authenticated user.
42+
In practice it likely means that you have to place the middleare at the very bottom of `middlewareGroups` in `app/Http/Kernel`
43+
44+
#### Parsing incoming initiator
45+
46+
Add `Ensi\LaravelInitiatorPropagation\ParseInitiatorHeaderMiddleware` to `app/Http/Kernel` middleware property.
47+
This middleware parses `X-Initiator` HTTP header, deserializes it into `InitiatorDTO` object and places it to the `InitiatorHolder` singleton.
48+
49+
#### Propagating initiator to outcomming HTTP request
50+
The package provides a `Ensi\InitiatorPropagation\PropagateInitiatorGuzzleMiddleware` Guzzle Middleware that converts ` resolve(InitiatorHolder::class)->getInitiator()` back to `X-Inititator` header and sets this header for all outcomming guzzle request.
51+
52+
You can add it to your guzzle stack like this:
53+
54+
```php
55+
$handlerStack = new HandlerStack(Utils::chooseHandler());
56+
$handlerStack->push(new PropagateInitiatorGuzzleMiddleware());
57+
```
58+
59+
### CLI
60+
61+
#### Artisan Commands
62+
63+
There is a custom artisan `Ensi\LaravelInitiatorPropagation\SetInitiatorArtisanMiddleware` that sets new initiator in every artisan command that you run.
64+
You can add it to the `app\Console\Kernel` like that:
65+
66+
```php
67+
public function bootstrap()
68+
{
69+
parent::bootstrap();
70+
(new SetInitiatorArtisanMiddleware())->handle();
71+
}
72+
```
73+
This middleware sets artisan command name (including argument, excluding options) as `$initiatorDTO->entrypoint`.
74+
If your custom artisan command makes guzzle HTTP requests to other apps the `PropagateInitiatorGuzzleMiddleware` uses this initiator.
75+
This middleware also works fine for [Laravel Task Scheduling](https://laravel.com/docs/latest/scheduling).
76+
77+
#### Queue Jobs
78+
79+
You typically want to persist initiator between incoming HTTP request and queued job.
80+
The package can help you here aswell. Unfortunately you need to touch a given job:
81+
82+
```php
83+
use Ensi\LaravelInitiatorPropagation\Job;
84+
85+
// Extend the job from package
86+
class TestJob extends Job implements ShouldQueue
87+
{
88+
public function __construct(protected Customer $customer)
89+
{
90+
// Do not forget to call parent constuctor
91+
parent::__construct();
92+
}
93+
94+
public function handle()
95+
{
96+
// Initiator is automatically persisted to InitiatorHolder via job middleware in parent class,
97+
// You do not need to persist it manually
98+
}
99+
}
100+
```
101+
102+
#### Laravel Queable Actions
103+
104+
If you use [spatie/laravel-queueable-action](https://github.com/spatie/laravel-queueable-action) package to dispatch actions instead of jobs you do not need to mess with every job separately.
105+
106+
Just publish `laravel-queueable-action` config and set the special Job class there:
107+
108+
```php
109+
'job_class' => \Ensi\InitiatorPropagation\ActionJob::class,
110+
```
25111

26112
## Contributing
27113

config/initiator-propagation.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,25 @@
22

33
return [
44
'app_code' => '',
5-
'default_user_type' => '',
5+
'set_initiator_http_middleware' => [
6+
'default_user_type' => '',
7+
8+
/**
9+
* Middleware parses this header to get `appCode`.
10+
* If the header is not specified here or in a request, `appCode` is taken from `app_code` config value
11+
*/
12+
'app_code_header' => '',
13+
14+
/**
15+
* Middleware parses this header to get `correlationId`
16+
* If the header is not specified here or in a request, `correlationId` is generated from scratch.
17+
*/
18+
'corelation_id_header' => '',
19+
20+
/**
21+
* Middleware parses this header to get `startedAt`
22+
* If the header is not specified here or in a request, `startedAt` is generated from scratch.
23+
*/
24+
'started_at_header' => '',
25+
]
626
];
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Ensi\LaravelInitiatorPropagation;
4+
5+
6+
use Ensi\InitiatorPropagation\InitiatorDTO;
7+
use Ensi\InitiatorPropagation\InitiatorHolder;
8+
use Illuminate\Container\Container;
9+
10+
class SetInitiatorArtisanMiddleware
11+
{
12+
public function handle(): void
13+
{
14+
Container::getInstance()->make(InitiatorHolder::class)->setInitiator(InitiatorDTO::fromScratch(
15+
app: config('initiator-propagation.app_code'),
16+
entrypoint: $this->extractEntrypointFromInput(),
17+
));
18+
}
19+
20+
protected function extractEntrypointFromInput(): string
21+
{
22+
$argv = $_SERVER['argv'] ?? [];
23+
$argvWithoutOptions = array_filter($argv, fn ($arg) => !str_starts_with($arg, '-'));
24+
25+
return implode(" ", $argvWithoutOptions);
26+
}
27+
}

src/SetInitiatorHttpMiddleware.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Ensi\LaravelInitiatorPropagation;
4+
5+
use Closure;
6+
use Ensi\InitiatorPropagation\InitiatorDTO;
7+
use Ensi\InitiatorPropagation\InitiatorHolder;
8+
use Illuminate\Container\Container;
9+
use Illuminate\Http\Request;
10+
11+
class SetInitiatorHttpMiddleware
12+
{
13+
public function handle(Request $request, Closure $next): mixed
14+
{
15+
$user = $request->user();
16+
$config = config('initiator-propagation', []);
17+
$mc = config('initiator-propagation.set_initiator_http_middleware', []);
18+
19+
$initiator = InitiatorDTO::fromScratch(
20+
userId: $user ? $user->getId() : "",
21+
userType: $user ? config('initiator-propagation.set_initiator_http_middleware.default_user_type', '') : "",
22+
app: !empty($mc['app_code_header']) ? $request->header($mc['app_code_header']) : ($config['app_code'] ?? ''),
23+
entrypoint: $this->extractEntrypoint($request),
24+
correlationId: !empty($mc['correlation_id_header']) ? $request->header($mc['correlation_id_header']) : '',
25+
startedAt: !empty($mc['started_at_header']) ? $request->header($mc['started_at_header']) : ''
26+
);
27+
28+
Container::getInstance()->make(InitiatorHolder::class)->setInitiator($initiator);
29+
30+
return $next($request);
31+
}
32+
33+
protected function extractEntrypoint(Request $request): string
34+
{
35+
if ($request->route()?->uri) {
36+
return "/" . ltrim($request->route()?->uri, "/");
37+
}
38+
39+
return $request->getRequestUri();
40+
}
41+
}

src/SetInitiatorMiddleware.php

Lines changed: 0 additions & 35 deletions
This file was deleted.

0 commit comments

Comments
 (0)