Boussolle

East Foundation

Créer des applications PHP en implémentant la philosophie #East programming.


Pour intégrer votre projet suivant le #East programming avec votre framework favori tout en gardant ce paradigme.

La bibliothèque implémente le pattern "Middleware" pour vous permettre de compléter son comportement, tel qu'ajouter des fonctionnalités, le support des sessions ou des traductions.

Cette bibliothèque est construite sur la bibliothèque "Recipe" et surchage uniquement certaines interfaces afin de correspondre au contexte HTTP. Elle peut être néamoins utilisée pour des workers.

  • Les Middlewares sont des actions, mais ils implémentent une interface spécifique.
  • Le workflow HTTP est définie via une "Recipe", capable d'être complétée.
  • Le Chef devient un "Manager" et exécute le workflow définie pour les requêtes HTTP.
  • Gestion des tâches asynchrones (grâce à pcntl) via des timers.
  • Mécanisme permettant de mettre en place un healthcheck des workers.
  • Fournit une fonction sleep non bloquante.
  • Utilisable avec n'importe quel framework PSR-11 ou avec Symfony 6.4+
Workflow web

Pour illustrer la philosophie "East", pensez à une boussole : comme le nord, le sud, l'est, l'ouest ; l'idée est que la direction "ouest" est avec état (vous appelez des méthodes et travaillez sur les valeurs de retour), tandis que la direction "est" est sans état (vous transmettez des lambdas ou des implémentations d'interface).

http://www.draconianoverlord.com/2013/04/12/east-oriented-programming.html

Fonctionnalités


East

Utiliser le #East programming avec votre framework préféré.

PSR-7

Interopérable avec tout code exploitant la recommandation PSR-7.

PSR-11

Interopérable avec tout framework utilisant la recommandation PSR-11.

Extensible

Grâce à Recipe, peut-être complété uniquement via la configuration de la DI.

GitHub


Cloner le projet sur GitHub

Projet sous licence open source! Hébergé, développé et maintenu sur GitHub.


Voir le projet sur GitHub

Patreon


Supporter le projet sur Patreon

Ce projet est logiciel libre et le restera, mais son développement a un coût. Si vous l'appréciez et si vous souhaitez nous aider à le maintenir et à le faire évoluer. N'hésitez pas à nous supporter sur Patreon.


Supporter le p[rojet

Exemple



<?php

declare(strict_types=1);

use
Psr\Http\Message\MessageInterface;
use
Teknoo\East\Foundation\Client\ResponseInterface;
use
Teknoo\East\Foundation\Router\ResultInterface;
use
DI\ContainerBuilder;
use
Laminas\Diactoros\ServerRequest;
use
Laminas\Diactoros\Response\TextResponse;
use
Teknoo\East\Foundation\Client\ClientInterface;
use
Teknoo\East\Foundation\Manager\ManagerInterface;
use
Teknoo\East\Foundation\Middleware\MiddlewareInterface;
use
Teknoo\East\Foundation\Recipe\RecipeInterface;
use
Teknoo\East\Foundation\Router\Result;
use
Teknoo\East\Foundation\Router\RouterInterface;

use function
DI\decorate;

require_once
'vendor/autoload.php';

//Simulate client, accepts responses from controller and pass them to the "framework" or lower layer to send them to
//the browser.
$client = new class implements ClientInterface {
private
ResponseInterface | MessageInterface | null $response = null;

private
bool $inSilentlyMode = false;

public function
updateResponse(callable $modifier): ClientInterface
{
$modifier($this, $this->response);

return
$this;
}

public function
acceptResponse(ResponseInterface | MessageInterface $response): ClientInterface
{
$this->response = $response;

return
$this;
}

public function
sendResponse(
ResponseInterface | MessageInterface | null $response = null,
bool $silently = false
): ClientInterface
{
$silently = $silently || $this->inSilentlyMode;

if (
null !== $response) {
$this->acceptResponse($response);
}

if (
true === $silently && null === $this->response) {
return
$this;
}

if (
$this->response instanceof MessageInterface) {
print
$this->response->getBody() . PHP_EOL;
} else {
print
$this->response . PHP_EOL;
}

return
$this;
}

public function
errorInRequest(Throwable $throwable, bool $silently = false): ClientInterface
{
print
$throwable->getMessage() . PHP_EOL;

return
$this;
}

public function
mustSendAResponse(): ClientInterface
{
$this->inSilentlyMode = false;

return
$this;
}

public function
sendAResponseIsOptional(): ClientInterface
{
$this->inSilentlyMode = true;

return
$this;
}
};

//First controller / endpoint, dedicated for the request /foo
$endPoint1 = static function (MessageInterface $message, ClientInterface $client): void {
$client->sendResponse(
new
TextResponse('request /bar, endpoint 1, value : ' . $message->getQueryParams()['value'])
);
};

//Second controller / endpoint, dedicated for the request /bar
$endPoint2 = static function (ClientInterface $client, string $value) {
$client->sendResponse(
new class (
$value) implements ResponseInterface {
public function
__construct(
private
string $value,
) {
}

public function
__toString(): string
{
return
"request /bar, endpoint 2, value : {$this->value}";
}
}
);
};

/**
* Simulate router
*/
$router = new class($endPoint1, $endPoint2) implements RouterInterface {
/**
* @var callable
*/
private $endPoint1;

/**
* @var callable
*/
private $endPoint2;

public function
__construct(callable $endPoint1 , callable $endPoint2)
{
$this->endPoint1 = $endPoint1;
$this->endPoint2 = $endPoint2;
}

public function
execute(
ClientInterface $client ,
MessageInterface $message,
ManagerInterface $manager
): MiddlewareInterface
{
$uri = (string) $message->getUri();

$manager->updateWorkPlan([
ResultInterface::class => match ($uri) {
'/foo' => new Result($this->endPoint1),
'/bar' => new Result($this->endPoint2),
},
]);
$manager->continueExecution($client, $message);

return
$this;
}
};

$builder = new ContainerBuilder();
$builder->addDefinitions('src/di.php');
$builder->addDefinitions([
RouterInterface::class => $router,

RecipeInterface::class => decorate(function ($previous) use ($router) {
if (
$previous instanceof RecipeInterface) {
$previous = $previous->registerMiddleware(
$router,
RouterInterface::MIDDLEWARE_PRIORITY
);
}

return
$previous;
})
]);

$container = $builder->build();

//Simulate Server request reception
$request1 = new ServerRequest([], [], '/foo', 'GET');
$request1 = $request1->withQueryParams(['value' => 'bar']);
$request2 = new ServerRequest([], [], '/bar', 'GET');
$request2 = $request2->withQueryParams(['value' => 'foo']);

$manager = $container->get(ManagerInterface::class);
$manager->receiveRequest($client, $request1);
//Print: request /bar, endpoint 1, value : bar
$manager->receiveRequest($client, $request2);
//Print: request /bar, endpoint 2, value : foo