Compass

East Foundation

Create PHP applications following the #East programming philosophy.


To integrate your code, following the #east programming philosophy, to your framework, while keeping this paradigm.

This library implement a middleware pattern to allow additional behavior or feature, like session or translation.

This library is built on the Recipe library, and redefine only some interfaces to be more comprehensive with HTTP context. It can be also used for workers.

  • Middleware are actions, but must implement a specific interface.
  • The HTTP workflow is defined into a Recipe, able to be extended.
  • Chef became a manager, to execute the workflow when a request is accepted.
  • Triggering asynchronous tasks (thanks to pcntl) for timers.
  • Setting up a worker health check
  • Provides non blocking sleep method
  • Usable with any PSR-11 Framework, Symfony implementation is also provided.
HTTP Workflow

To understand “east”, think of a map, like north, south, east, west; the idea, as far as I understand, is that west-oriented is stateful (you call methods and work on the return values), while east-oriented is stateless (you pass lambdas or interface implementations).

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

Features


East

Can implement your #East package in your framework while keeping this philosophy.

PSR-7

Interoperable with any code following the PSR-7 recommendation.

PSR-11

Interoperable with any framework implementing the PSR-11 recommendation.

Extendable

Thanks to Recipe, can be extendable only via the DI's configuration.

GitHub


Fork the project on GitHub

It is open source! Hosted, developed, and maintained on GitHub.


View GitHub Project

Patreon


Support this project on Patreon

This project is free and will remain free, but its development is not. If you like it and help us maintain it and evolve it, don't hesitate to support us on Patreon.


Support it

Example



<?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