teknoo/east-foundation
teknoo/east-foundation provides core PHP building blocks for the East framework, including dependency injection, service configuration, and reusable foundation components to bootstrap modular applications.
Install via Composer (PHP 8.1+, PSR-2/12, strict types):
composer require teknoo/east-foundation
Before writing a line of code, internalize the #east philosophy: every public method returns either $this (mutable/fluent) or a new instance of $this (immutable). Nothing returns void, null, arrays of results, or scalars from a public method on your domain objects. If you write a method that "does something and returns nothing," you have broken the contract.
Where to look first:
src/Http/Message/ for ChunkGeneratorInterface and SseGeneratorInterface — this is the most immediately useful surface.infrastructures/laminas/ for CallbackStream and CallbackStreamFactoryInterface — the reference implementation of streaming.Teknoo\Immutable, Teknoo\States, and Teknoo\Recipe — the three packages east-foundation leans on. Recipe is where your request orchestration lives; States gives you method availability by state; Immutable gives you the with*() value-object pattern.teknoo/east-foundation-bundle (or the HttpFoundationFactory service) if you want a StreamedResponse without wiring it yourself.First use case — server-sent events or chunked streaming. In practice most people arrive here because they want to push a generator of strings to an HTTP client and stop cleanly when the client disconnects. Define a generator implementing SseGeneratorInterface (yield ['event-name' => $data], use the COMMENT key for keep-alive comments), hand it to CallbackStreamFactoryInterface::createStreamFromGenerator(), and on Symfony let HttpFoundationFactory convert the CallbackStream into a StreamedResponse. Set status and headers before calling acceptResponse(); the generator runs afterwards.
Recipe-driven orchestration. Do not put HTTP concern into controllers. Model a request as a Teknoo\Recipe of steps. Each step mutates or replaces state and returns $this/a new instance. The final step calls acceptResponse(). This keeps your controller thin and makes each step independently testable — which matters because the project requires 90% coverage on new code.
Immutable value objects. For domain objects, expose withX($value) returning new static(...) (or clone), and reserve fluent $this returns for mutable builders. Never mix the two conventions in the same class — the ambiguity causes bugs when callers chain and unwittingly mutate a shared instance. Teknoo\Immutable gives you the trait scaffolding.
Fluent builders. Config objects, stream configurators, and container wiring are the right place for the mutable return $this pattern:
$factory->withStatus(200)->withHeader('X-Accel-Buffering', 'no');
States for conditional APIs. Teknoo\States lets you declare that a method is only valid in a certain state (e.g., a stream can only be write()-ed before acceptResponse()). Push precondition logic into states rather than if guards; it documents the lifecycle and fails loudly.
Streaming integration pattern (Symfony). Inject the HttpFoundationFactory service, build your CallbackStream, and return its StreamedResponse. For PSR-11-only frameworks (Laminas, Mezzio), return the CallbackStream directly and let your runtime's emitter handle it. The stream_type metadata must be chunk or sse — set it explicitly, or the factory will not know how to wrap the response.
PSR-15 middleware. Compose request/response middleware the normal PSR way — the package is framework-agnostic by design, so middleware you write against PSR-7/PSR-15 should be portable across Symfony and Laminas. Keep middleware side-effect free and return a new response; do not mutate the inbound request.
Symfony vs bare PSR-11. If you're on Symfony 6+, prefer the bundle: it wires CallbackStreamFactoryInterface and HttpFoundationFactory for you. On a PSR-11 container, you must bind those two interfaces manually in your service definitions — there is no auto-discovery.
$this vs new-instance rule is a hard lint. Reviewers will reject void returns and any public method that returns a scalar or array. If you find yourself returning bool for "did it work," throw an exception instead — the philosophy assumes success and uses exceptions for failure.acceptResponse(). Status code and headers must be set first; the generator is only consumed after. Setting a header inside the generator is too late — output has already begun.CallbackStream writes and flushes each chunk and stops when the client closes. If you buffer manually or use output buffering, you break that detection — turn off output buffering (and PHP's output_buffering setting) before the stream runs.COMMENT key periodically so proxies don't kill idle connections. An empty event name with a comment payload is the idiomatic way.stream_type metadata is required. Forgetting to tag the stream as chunk or sse is the most common cause of HttpFoundationFactory returning a plain response instead of a StreamedResponse. Symptom: response arrives all at once at the end.make qa before committing — it bundles PHPStan, linting, and security audits. make test runs PHPUnit plus Behat feature tests.feature/ or hotfix/ branches and one concern per PR. Automated agents follow the same rule, so expect tight review.src/. Anything in the core library that references Symfony\Component\... outside of an infrastructure folder is a bug. Infrastructures are swappable; core must stay PSR-only.ChunkGeneratorInterface/SseGeneratorInterface for custom producers; implement CallbackStreamFactoryInterface if you need a different emitter (e.g., Swoolehole or ReactPHP); bind your own HttpFoundationFactory wrapper if you need custom response decoration.How can I help you explore Laravel packages today?