<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Semitexa</title>
    <description>The latest articles on DEV Community by Semitexa (semitexa).</description>
    <link>https://dev.to/semitexa</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14814%2Fdbd371f9-1495-4731-99e2-5c77bc62ef19.png</url>
      <title>DEV Community: Semitexa</title>
      <link>https://dev.to/semitexa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/semitexa"/>
    <language>en</language>
    <item>
      <title>From Idea to First Customer: How Semitexa Shortens Time to Market</title>
      <dc:creator>Taras Hanych</dc:creator>
      <pubDate>Mon, 28 Sep 2026 10:40:15 +0000</pubDate>
      <link>https://dev.to/semitexa/from-idea-to-first-customer-how-semitexa-shortens-time-to-market-767</link>
      <guid>https://dev.to/semitexa/from-idea-to-first-customer-how-semitexa-shortens-time-to-market-767</guid>
      <description>&lt;p&gt;&lt;em&gt;The first release of a business product has one job: help a real customer complete something valuable. Every week spent rebuilding common software foundations is a week before you can learn from that customer.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The clock stops at a customer outcome
&lt;/h2&gt;

&lt;p&gt;A deployed homepage is a milestone. A usable business is a different one. Imagine a client portal: a customer signs in, submits a request, sees its status, and receives an update when the team acts. The team can see who owns the request and respond. That complete loop creates value and produces feedback worth acting on.&lt;/p&gt;

&lt;p&gt;Time to market is the time until that loop works for its first customer. It includes product decisions, implementation, review and release. The shorter that path, the sooner a founder can validate demand, refine pricing and find out what customers actually need next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Most launch delays hide between the features
&lt;/h2&gt;

&lt;p&gt;The visible screen is often the easy part. A real client portal also needs identity and permissions, persistent data, an interface people can use, notifications, background work and a way to check that one customer's information stays with that customer. Teams can spend their early budget joining these pieces before they get to the workflow that makes the product distinct.&lt;/p&gt;

&lt;p&gt;Semitexa addresses that integration work with a coherent set of modules and conventions. Its Framework has typed routes and handlers, data access, authentication and authorization, tenant context, events, queues and API capabilities. Its Platform provides reusable interface building blocks. The ecosystem describes the OS as the experience layer, Platform as the interface layer and Framework as the execution layer. Each layer gives the team a clearer starting point for a different part of the product.&lt;/p&gt;

&lt;p&gt;These capabilities still need product choices and configuration. Their value is that the team can make those choices around an existing structure, then spend more of its time on the customer journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the differentiator on a connected foundation
&lt;/h2&gt;

&lt;p&gt;For the portal example, the differentiator might be how requests are assessed, routed and resolved. Semitexa lets a team focus on that flow while using established paths for the surrounding work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Move from screen to behavior.&lt;/strong&gt; A page generator can scaffold the payload, handler, resource and template together, so the first interaction has an application path behind it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep customer boundaries visible.&lt;/strong&gt; Tenant resolution and tenant-aware data patterns give multi-customer products a place to express ownership early.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let work continue after the response.&lt;/strong&gt; Events and queues provide a path for notifications and longer tasks without making the customer wait on the page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expose the same operation where it is needed.&lt;/strong&gt; Typed API routes can support other clients and integrations as the product grows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can inspect these ideas in the running Framework Demo: &lt;a href="https://framework.semitexa.com/demo/cli/scaffolding-generators" rel="noopener noreferrer"&gt;scaffolding&lt;/a&gt;, &lt;a href="https://framework.semitexa.com/demo/platform/tenancy-isolation" rel="noopener noreferrer"&gt;tenant data isolation&lt;/a&gt; and &lt;a href="https://framework.semitexa.com/demo/events/queued" rel="noopener noreferrer"&gt;queued work&lt;/a&gt; each have their own walkthrough. The examples are useful because a buyer can look past a feature list and see the implementation shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  Release one complete workflow first
&lt;/h2&gt;

&lt;p&gt;A focused first version of the portal could be deliberately small. Let a customer submit one request type. Let the team review it and change its status. Show the change back to the customer and send one notification. Put access rules and data ownership around that path. Then invite a real customer to use it.&lt;/p&gt;

&lt;p&gt;That sequence gives a team something more useful than a large unfinished feature list: a working transaction to observe. The first conversations will reveal whether the request form asks the right questions, whether the team needs another status, and which update customers value. Semitexa's structure helps the team change the application without starting a second architecture each time the product learns.&lt;/p&gt;

&lt;p&gt;The fastest launch is rarely the one with the most features. It is the one that reaches a trustworthy customer outcome with the least avoidable construction along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed matters after the first release, too
&lt;/h2&gt;

&lt;p&gt;Going live early helps only if the team can keep improving the product. Semitexa includes project-aware inspection, dependency impact queries and focused verification commands. Those tools help developers understand what a change touches and check it before release. They do not replace testing or product judgment; they shorten the distance from a customer observation to a reviewable change.&lt;/p&gt;

&lt;p&gt;This is why Semitexa is a strong route to market for customer portals, SaaS workflows and operational software. The product's first useful path can be assembled from existing application capabilities, while the team keeps ownership of the business rules that make the venture worth building.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the first customer, not a feature inventory
&lt;/h2&gt;

&lt;p&gt;If you are planning a product, describe the first person who will use it and the one job they must be able to finish. Add what happens immediately afterward: who reviews it, what data belongs to whom, and what message needs to be sent. That is enough to shape a useful first release and identify which Semitexa capabilities belong in it.&lt;/p&gt;

&lt;p&gt;Bring us that workflow. We can turn it into a concrete launch scope, show the technical path and make the next decision about your business easier.&lt;/p&gt;




&lt;h2&gt;
  
  
  What must your first customer be able to do?
&lt;/h2&gt;

&lt;p&gt;Tell us about the workflow, the people involved and the launch goal. We will help you find the shortest credible path to a working product with Semitexa.&lt;/p&gt;

&lt;p&gt;&lt;a href="mailto:support@semitexa.com?subject=Build%20my%20product%20with%20Semitexa" class="crayons-btn crayons-btn--primary"&gt;Discuss your project&lt;/a&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://framework.semitexa.com/demo/get-started/installation" rel="noopener noreferrer"&gt;Explore the Framework Demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was first published on the &lt;a href="https://semitexa.com/blog/from-idea-to-first-customer" rel="noopener noreferrer"&gt;Semitexa blog&lt;/a&gt;, where the live demos run next to the text. Semitexa is on &lt;a href="https://github.com/semitexa" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>saas</category>
      <category>productivity</category>
      <category>php</category>
    </item>
    <item>
      <title>The Tenant Must Travel with the Work</title>
      <dc:creator>Taras Hanych</dc:creator>
      <pubDate>Mon, 28 Sep 2026 10:39:53 +0000</pubDate>
      <link>https://dev.to/semitexa/the-tenant-must-travel-with-the-work-jne</link>
      <guid>https://dev.to/semitexa/the-tenant-must-travel-with-the-work-jne</guid>
      <description>&lt;p&gt;&lt;em&gt;The browser knows which customer is working. The queue knows that something needs doing. A reliable SaaS application has to preserve the connection between those two facts.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The request ends before the responsibility does
&lt;/h2&gt;

&lt;p&gt;Imagine a customer uploading a product image. The application accepts it, schedules a thumbnail, and returns a success response. A worker will finish the expensive part later. To the customer, this is one action: “prepare my image.” To the software, it is already two executions.&lt;/p&gt;

&lt;p&gt;The second execution may start after the browser tab has closed. It may run in another process. The hostname that identified the customer is gone, along with the request that carried it. Yet the thumbnail still belongs to the same organization, under the same business rules.&lt;/p&gt;

&lt;p&gt;This is an illustrative scenario, but the architectural boundary is real. Moving work into a queue also moves responsibility for its context. A job can arrive intact and still arrive without enough information to run correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  A perfectly delivered message can be incomplete
&lt;/h2&gt;

&lt;p&gt;A message containing an asset ID and a transformation name tells a worker what to process. Depending on the application, it may leave other questions unanswered: which organization’s configuration should apply, which locale should an accompanying notification use, and which environment does this work belong to?&lt;/p&gt;

&lt;p&gt;Those answers might be recoverable from the asset. That can be a valid design. The fragile version is the one where each downstream service guesses differently, or reads whatever “current tenant” happens to be left in memory. Local testing with one customer rarely challenges that assumption. A shared worker serving many customers will.&lt;/p&gt;

&lt;p&gt;Adding a tenant field to a database table addresses one part of the design. Preserving the execution context across a queue is another. Semitexa makes that handoff explicit with a shared tenant context and a serializer at the message boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the context part of the handoff
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;TenantAwareJobSerializer::wrap()&lt;/code&gt; reads the current context from &lt;code&gt;TenantContextStore&lt;/code&gt;. For a concrete, non-default tenant, it adds an envelope under &lt;code&gt;_tenant&lt;/code&gt;. The receiving side can extract that envelope and rebuild the context before application work begins.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Resolve:&lt;/strong&gt; establish the tenant for the incoming execution.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Attach:&lt;/strong&gt; serialize the supported context into the message.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Restore:&lt;/strong&gt; recover that context at the worker boundary.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Execute:&lt;/strong&gt; run the operation with an explicit tenant context available.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Clear:&lt;/strong&gt; end the context’s lifetime when the execution finishes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The queue separates two executions. The envelope connects their tenant context; the execution lifecycle owns cleanup.&lt;/p&gt;

&lt;p&gt;An illustrative wrapped payload looks like this. The application fields are examples; &lt;code&gt;_tenant&lt;/code&gt; and its fields follow the serializer’s current format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assetId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image-42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"variantKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"thumbnail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"_tenant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tenantId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acme"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strategy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"subdomain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prod"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation serializes the organization ID and resolution strategy, plus locale and environment when those layers are explicitly present. It does not serialize every possible context layer. A custom layer needs an intentional propagation design of its own.&lt;/p&gt;

&lt;p&gt;There is also a deliberate default case: when no context exists, or the organization is the default tenant, wrapping leaves the payload unchanged. Consumers therefore need a policy for messages without &lt;code&gt;_tenant&lt;/code&gt;. A task that requires an organization should reject missing tenant identity at its boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  This already has a job in Semitexa
&lt;/h2&gt;

&lt;p&gt;The media package provides a concrete example. &lt;code&gt;MediaQueueDispatcher&lt;/code&gt; wraps a serialized transformation message before sending it. On the receiving side, &lt;code&gt;MediaWorker::processPayload()&lt;/code&gt; calls &lt;code&gt;unwrapAndRestore()&lt;/code&gt; before reconstructing the media message and processing it.&lt;/p&gt;

&lt;p&gt;Unwrapping removes the envelope from the application payload. Restoring places the recovered context into the store’s CLI fallback. The transformation message keeps its own domain shape, while services that consult the context store have a tenant available during execution.&lt;/p&gt;

&lt;p&gt;The media transformation path also passes the asset’s tenant ID explicitly when resolving collection policy and generating a variant. That detail matters: the queued context and the persisted asset identity play distinct roles. Application code still has to enforce the relationship between them wherever that relationship controls access or storage.&lt;/p&gt;

&lt;p&gt;This is the concrete problem Semitexa addresses: the HTTP request can disappear without making an integrated background operation reconstruct tenant context from ambient process state. The producer and consumer share one envelope format and one restoration mechanism. Custom queue paths must wire those mechanisms into their own execution boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worker must also know when to forget
&lt;/h2&gt;

&lt;p&gt;Now imagine the same worker processes a second message. It has no tenant envelope. If the first job’s context remains in memory, the second job may observe the first customer’s identity. Correct propagation into one job has become incorrect propagation into the next.&lt;/p&gt;

&lt;p&gt;This is where tenancy meets the &lt;a href="https://semitexa.com/blog/long-running-php-semitexa" rel="noopener noreferrer"&gt;long-running PHP lifecycle&lt;/a&gt;. In a Swoole coroutine, Semitexa’s tenant store reads coroutine-local state. Outside a coroutine, it uses a process fallback. Those storage choices support different execution environments and require a clear end to each unit of work.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TenantContextStore&lt;/code&gt; registers cleanup with &lt;code&gt;PerRequestStateRegistry&lt;/code&gt; when context is set. Its cleanup callback clears the context of the current execution: the coroutine-local slot inside a coroutine, the process fallback outside one. The surrounding runtime must actually invoke that lifecycle at the appropriate boundary.&lt;/p&gt;

&lt;p&gt;The serializer alone does not provide that boundary. In particular, &lt;code&gt;unwrapAndRestore()&lt;/code&gt; sets the fallback when an envelope exists; a message without an envelope does not make that method clear an earlier fallback. A custom worker loop must explicitly ensure cleanup, including on exceptions and early returns. A coroutine consumer must establish context in the coroutine’s own store rather than assuming the CLI fallback is visible there.&lt;/p&gt;

&lt;p&gt;Preserve the tenant across the handoff. End its lifetime after the work. Both steps belong in the design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put each guarantee where it can be enforced
&lt;/h2&gt;

&lt;p&gt;Once the context travels reliably, the rest of the application has a stable input. It still needs to use that input correctly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resolution&lt;/strong&gt; determines which organization an execution refers to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization&lt;/strong&gt; determines whether the actor or job may perform the action for that organization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data isolation&lt;/strong&gt; scopes queries, cache keys, storage paths, and other resources where the application accesses them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagation and cleanup&lt;/strong&gt; preserve context across handoffs and prevent it from surviving into unrelated work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The envelope carries identity; it does not prove permission. Queue ingress must be trusted or validated, tenant-owned records must be checked, and tenant-sensitive operations need an explicit policy for absent context. Semitexa supplies the context mechanisms. An application earns its isolation guarantee through the boundaries that consume them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the handoff with two customers
&lt;/h2&gt;

&lt;p&gt;A useful test goes beyond proving that one message round-trips through a serializer. Process work for tenant A, then tenant B, in the same worker. Follow both with a message that has no tenant envelope. Check what each operation can observe. Repeat the sequence with a failure during A’s work, so cleanup has to survive an exception.&lt;/p&gt;

&lt;p&gt;At the serialization level, Semitexa’s tenancy package includes dedicated propagation tests. At the application level, test the effects: which configuration was selected, which records were accessed, and where the output was stored. Also check any locale or environment values the workflow promises to preserve. These are acceptance criteria for an integration, not a claim that a serializer test proves every application safe.&lt;/p&gt;

&lt;p&gt;The larger lesson is useful well beyond media processing. Exports, notifications, and scheduled work all cross moments where the original request is no longer available. Making tenant context explicit turns those moments into boundaries that can be inspected and tested. A customer’s action can then continue after the response, with its ownership still accounted for.&lt;/p&gt;




&lt;h2&gt;
  
  
  Follow the lifetime of the work.
&lt;/h2&gt;

&lt;p&gt;Explore tenant propagation in the Framework guide, then see why execution boundaries matter in a process that stays alive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://framework.semitexa.com/demo/platform/tenancy-queue" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Explore tenant propagation&lt;/a&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://semitexa.com/blog/long-running-php-semitexa" rel="noopener noreferrer"&gt;Read about long-running PHP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was first published on the &lt;a href="https://semitexa.com/blog/tenant-context-background-work" rel="noopener noreferrer"&gt;Semitexa blog&lt;/a&gt;, where the live demos run next to the text. Semitexa is on &lt;a href="https://github.com/semitexa" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>saas</category>
      <category>php</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>Three Layers, One Workflow: OS, Platform, and Framework</title>
      <dc:creator>Taras Hanych</dc:creator>
      <pubDate>Mon, 28 Sep 2026 10:39:30 +0000</pubDate>
      <link>https://dev.to/semitexa/three-layers-one-workflow-os-platform-and-framework-2jc3</link>
      <guid>https://dev.to/semitexa/three-layers-one-workflow-os-platform-and-framework-2jc3</guid>
      <description>&lt;p&gt;&lt;em&gt;A good business tool needs a place to work, an interface that makes the work clear, and a system that carries it out. Semitexa gives those three jobs distinct names.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Three layers, three questions
&lt;/h2&gt;

&lt;p&gt;Think about a person reviewing an approval. They need to find the task, understand the details, and submit a decision. It is tempting to call all of that “the app.” Separating the responsibilities makes it easier to design the experience and to reason about the code behind it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgk9o2e2q63g1r05qvcxb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgk9o2e2q63g1r05qvcxb.png" alt="The three responsibilities of the Semitexa ecosystem" width="800" height="962"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Start with the human need, then decide which layer owns each part. The arrows show a design conversation, not a requirement that every request passes through three separate services.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  OS: where work has a home
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://os.semitexa.com/" rel="noopener noreferrer"&gt;Semitexa OS&lt;/a&gt; is the experience layer. Its job is the shape of a working day: how someone finds their projects, changes focus, and keeps context while moving between tools. A useful OS decision might be whether an approval is visible beside the project it belongs to, or how the workspace helps someone return to an unfinished task.&lt;/p&gt;

&lt;p&gt;That is a product question before it is a component question. A polished button cannot repair a workflow that leaves people lost. The OS site presents this operating surface as a product vision; the layer map describes the intended responsibility, not a claim that every possible workflow is already shipped end to end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform: how the work is presented
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://platform.semitexa.com/" rel="noopener noreferrer"&gt;Semitexa Platform&lt;/a&gt; is the interface layer: reusable fields, data views, product shells, and interaction patterns. If the same kind of approval appears in several products, this layer is where the visual grammar should remain consistent. The Platform showcase demonstrates server-owned rendering, components, deferred regions, and live updates.&lt;/p&gt;

&lt;p&gt;Reusability is useful only when it clarifies the task. A field should make its label, help, validation, and state legible. A data view should make scanning and acting on a row predictable. Platform supplies that common language while each product keeps its own purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework: how the decision runs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://framework.semitexa.com/" rel="noopener noreferrer"&gt;Semitexa Framework&lt;/a&gt; is the execution layer. In its typed PHP flow, a payload describes an incoming request, a handler makes the application decision, and a resource returns the result. Server-rendered pages, events, tenancy, and long-running work can share that explicit architecture.&lt;/p&gt;

&lt;p&gt;For an approval, application code still has to check who may act, apply the business rule, store the result, and decide whether an event should follow. A reusable interface does not make that decision for the server. The Framework site lets you inspect working routes and demos when you want to see how the underlying request and response fit together.&lt;/p&gt;

&lt;h2&gt;
  
  
  One workflow, three decisions
&lt;/h2&gt;

&lt;p&gt;Imagine an approval inbox. This is an illustrative feature, not a claim that a shared inbox is already deployed across all three sites.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Experience:&lt;/strong&gt; place the inbox where a person can find it in the context of their project, and make the next action obvious.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Interface:&lt;/strong&gt; show the request in a readable data view, present the decision with a consistent control, and explain any validation error.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Execution:&lt;/strong&gt; accept the action through a typed route, check permission and state in application logic, then return the updated result or publish follow-up work.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The boundaries help when a feature changes. If people cannot find the inbox, revisit the experience. If every inbox looks different, revisit the interface. If the action can be applied twice or by the wrong person, revisit execution. The separation turns a vague “the app feels wrong” into a question a team can answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start exploring
&lt;/h2&gt;

&lt;p&gt;Begin with the layer that matches your question. Explore the &lt;a href="https://os.semitexa.com/" rel="noopener noreferrer"&gt;OS vision&lt;/a&gt; to see the working environment, the &lt;a href="https://platform.semitexa.com/" rel="noopener noreferrer"&gt;Platform showcase&lt;/a&gt; for reusable interface patterns, and the &lt;a href="https://framework.semitexa.com/demo/get-started/installation" rel="noopener noreferrer"&gt;Framework installation guide&lt;/a&gt; when you are ready to trace or build the PHP flow. These sites currently show different parts of the ecosystem; the three-layer map gives them a shared design vocabulary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Follow the question to the right layer.
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://os.semitexa.com/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Explore OS&lt;/a&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://platform.semitexa.com/" rel="noopener noreferrer"&gt;Explore Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://framework.semitexa.com/" rel="noopener noreferrer"&gt;Explore Framework&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was first published on the &lt;a href="https://semitexa.com/blog/three-layers-one-workflow" rel="noopener noreferrer"&gt;Semitexa blog&lt;/a&gt;, where the live demos run next to the text. Semitexa is on &lt;a href="https://github.com/semitexa" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>php</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Long-Running PHP: Inside the Semitexa Runtime</title>
      <dc:creator>Taras Hanych</dc:creator>
      <pubDate>Mon, 28 Sep 2026 10:39:00 +0000</pubDate>
      <link>https://dev.to/semitexa/long-running-php-inside-the-semitexa-runtime-30f0</link>
      <guid>https://dev.to/semitexa/long-running-php-inside-the-semitexa-runtime-30f0</guid>
      <description>&lt;p&gt;&lt;em&gt;The first request works. The second remembers more than it should. Keeping a PHP application alive makes its lifetimes part of the architecture: which objects should survive, which data belongs to one execution, and what happens while two requests overlap? Semitexa was designed from day one around those questions.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Designed for long-running PHP from day one
&lt;/h2&gt;

&lt;p&gt;Imagine a dashboard showing an order as it moves from payment to fulfillment. The first page should arrive quickly. A slow recommendation service should not hold up the order details. A background job should be able to publish progress. Several people may be watching, each with their own session and permissions.&lt;/p&gt;

&lt;p&gt;That application benefits from more than a faster request handler. It needs reusable infrastructure, isolated execution state, efficient waiting, live delivery and a way to see what is happening inside the server. Semitexa treats those needs as parts of the same runtime design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semitexa was created for the long-running execution model from the beginning.&lt;/strong&gt; The worker is a first-class lifetime. The container distinguishes shared services from execution-scoped objects. Request context is carried through the framework. Coroutines, background work and streamed HTML have a place in the architecture.&lt;/p&gt;

&lt;p&gt;The practical advantage is consistency. You can follow a typed payload into a handler, resolve dependencies with explicit lifetimes, produce a resource, and render or stream it using the same application model. The framework can make decisions at startup because the resulting application will serve many executions.&lt;/p&gt;

&lt;p&gt;Other PHP runtimes also keep applications in memory; &lt;a href="https://frankenphp.dev/docs/worker/" rel="noopener noreferrer"&gt;FrankenPHP's worker documentation&lt;/a&gt; is a useful introduction to that broader model. This article examines Semitexa's own Swoole-based implementation. Its strength is how the framework's pieces are designed to work together within that model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The worker lives longer than the request
&lt;/h2&gt;

&lt;p&gt;In a conventional PHP-FPM deployment, a worker process can already serve many requests. The application is normally initialized within each request's PHP execution, with ordinary userland request state torn down afterward. Long-running application servers keep the initialized application itself available across requests.&lt;/p&gt;

&lt;p&gt;OPcache and application persistence address different work. &lt;a href="https://www.php.net/manual/en/book.opcache.php" rel="noopener noreferrer"&gt;OPcache&lt;/a&gt; caches compiled script bytecode. A persistent application can also reuse its bootstrapped service graph and discovered metadata. Neither mechanism makes database queries, remote APIs or expensive business logic disappear.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Worker starts   → discover and build      Warm application
Request A       → its context + handler   Response A
Request B       → its context + handler   Response B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;One worker can reuse its application while each request receives its own execution state. With coroutines, request A and request B may overlap while waiting for I/O. This is an architectural diagram, not a timing measurement.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The useful distinction is ownership. A service containing stable behavior can live with the worker. The current customer's identity belongs to an execution. A database connection is a resource to borrow for bounded work. A cross-worker counter needs an explicitly shared store.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lifetime&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;th&gt;Design consequence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Worker&lt;/td&gt;
&lt;td&gt;Shared services, container metadata, handler prototypes&lt;/td&gt;
&lt;td&gt;Reuse them; keep request identity out of their mutable fields.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution&lt;/td&gt;
&lt;td&gt;Handler clone, request, auth, tenant and locale context&lt;/td&gt;
&lt;td&gt;Resolve them for the current execution and release them afterward.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Borrowed operation&lt;/td&gt;
&lt;td&gt;Database or Redis connection&lt;/td&gt;
&lt;td&gt;Return it after use; bound how long a caller can wait.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicitly shared state&lt;/td&gt;
&lt;td&gt;Database records, Redis data, selected Swoole tables&lt;/td&gt;
&lt;td&gt;Choose storage and atomic operations appropriate to the sharing boundary.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A PHP static property is local to its process and can survive many executions. It is therefore a poor place for “the current user,” and it is not automatically a counter shared by every worker. Understanding both boundaries prevents two very different classes of mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it: a persistent worker, a fresh handler
&lt;/h2&gt;

&lt;p&gt;The following values are produced by the PHP handler serving this article. Its &lt;code&gt;$handled&lt;/code&gt; property starts at zero and is incremented when &lt;code&gt;handle()&lt;/code&gt; runs. Semitexa's execution-scoped resolution supplies a fresh handler clone for the next request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://semitexa.com/blog/long-running-php-semitexa" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;▶ Run the live demo — Which state survived?&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Run it again. The handler counter should still be &lt;strong&gt;1&lt;/strong&gt;. You may see the same worker process ID, or another one when the server distributes requests across workers. A reload or deployment can also change the process ID. A repeated ID with a fresh counter illustrates the two lifetimes directly.&lt;/p&gt;

&lt;p&gt;This is an actual PHP result, with caching disabled for the response. It is a small demonstration of handler resolution; it does not by itself prove concurrent tenant isolation or measure throughput. We will run the relevant concurrency tests later.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PHP · the handler's scalar state starts fresh on each resolved clone&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BlogArticleHandler: excerpt from the handler serving this page.&lt;/span&gt;
&lt;span class="c1"&gt;// #[AsPayloadHandler] makes the handler execution-scoped.&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$handled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Inside handle(), when this article is requested:&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$resource&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withRuntimeProbe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;workerPid&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;getmypid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;executionCount&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;handled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;renderedAt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;gmdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d\TH:i:s\Z'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler does not contain a counter-reset instruction. The lifetime is established when the container resolves it. If an application deliberately keeps that clone in a global variable, or puts the counter in a static property, it has chosen a different lifetime.&lt;/p&gt;

&lt;h2&gt;
  
  
  A dependency container that knows what should survive
&lt;/h2&gt;

&lt;p&gt;Semitexa's container performs module discovery, attribute scanning, contract resolution, injection analysis and graph construction during its build. It stores shared service instances and execution-scoped prototypes, then seals registration. Each HTTP worker builds its container during startup.&lt;/p&gt;

&lt;p&gt;That moves structural work out of the ordinary request path. The worker can reuse known bindings and injection metadata while resolving the objects needed for the next execution. The payoff is less repeated bootstrapping and a clear place to diagnose wiring problems.&lt;/p&gt;

&lt;p&gt;Two injection attributes make the lifetime distinction visible in application code. &lt;code&gt;#[InjectAsReadonly]&lt;/code&gt; supplies a worker-scoped dependency. &lt;code&gt;#[InjectAsMutable]&lt;/code&gt; supplies execution-dependent values or services when the scoped object is cloned. Payload handlers, event listeners and pipeline listeners imply execution scope; other applicable services can declare &lt;code&gt;#[ExecutionScoped]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PHP · property injection expresses which dependency belongs to which lifetime&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Properties on an execution-scoped payload handler:&lt;/span&gt;
&lt;span class="na"&gt;#[InjectAsReadonly]&lt;/span&gt;
&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;SiteBlogCatalog&lt;/span&gt; &lt;span class="nv"&gt;$blog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[InjectAsMutable]&lt;/span&gt;
&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// The catalog is reusable across executions.&lt;/span&gt;
&lt;span class="c1"&gt;// Request is resolved from the current execution context.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shared attribute describes the injection lifetime; it does not make every property inside the injected object immutable. A shared service still needs appropriate state discipline. Similarly, PHP cloning is shallow: arbitrary mutable child objects do not become independent just because their parent was cloned. Execution-dependent dependencies should use the framework's scoped resolution.&lt;/p&gt;

&lt;p&gt;You can inspect the implementation in &lt;a href="https://github.com/semitexa/semitexa-core/blob/17861784cb29ea1092b97001024f495200465e71/src/Container/ContainerBootstrapper.php" rel="noopener noreferrer"&gt;ContainerBootstrapper&lt;/a&gt; and &lt;a href="https://github.com/semitexa/semitexa-core/blob/17861784cb29ea1092b97001024f495200465e71/src/Container/SemitexaContainer.php" rel="noopener noreferrer"&gt;SemitexaContainer&lt;/a&gt;. The latter's resolution path clones scoped prototypes and injects the current context. This is the mechanism behind the counter above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let requests overlap without sharing their identity
&lt;/h2&gt;

&lt;p&gt;A request waiting for a database response should not have to monopolize a worker that could serve another request. Semitexa's server enables Swoole coroutines and runtime hooks before creating the HTTP server. Supported blocking operations can yield so another coroutine can make progress.&lt;/p&gt;

&lt;p&gt;This is especially useful for I/O-heavy pages, upstream API calls and long-lived streams. It does not turn a CPU-heavy PHP loop into parallel work, and runtime hooks only cover supported operations in the installed Swoole build. CPU-bound work still needs an appropriate process or job strategy.&lt;/p&gt;

&lt;p&gt;Overlapping requests make isolation more demanding. Clearing a global “current user” at the end of a request cannot protect another request that was already running while the first one yielded. The current identity must be attached to the execution that owns it throughout the overlap.&lt;/p&gt;

&lt;p&gt;Semitexa's &lt;code&gt;ExecutionContext&lt;/code&gt; carries request, session, cookies, tenant, auth and locale values. The container stores those bindings through &lt;code&gt;CoroutineLocal&lt;/code&gt;, which uses Swoole's coroutine context during coroutine execution. Resolving a mutable dependency after a yield therefore reads the current coroutine's context.&lt;/p&gt;

&lt;p&gt;The container itself remains shared. The execution bindings do not become an ordinary field on that shared container. This separation is one of the most important strengths of a framework designed around persistent, concurrent workers.&lt;/p&gt;

&lt;p&gt;Child coroutines have a deliberate boundary too. A new child does not automatically inherit its parent's execution context. Framework code that needs propagation captures the context and applies it explicitly:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PHP · explicit propagation when writing a custom coroutine integration&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Runtime integration code with an execution-context-aware container.&lt;/span&gt;
&lt;span class="nv"&gt;$snapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;captureExecutionContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;\Swoole\Coroutine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$snapshot&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;runWithExecutionContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Resolve context-dependent services inside this callback.&lt;/span&gt;
        &lt;span class="nv"&gt;$request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;captureExecutionContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Perform the child operation using its explicit context.&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;runWithExecutionContext()&lt;/code&gt; restores the previous bindings in a &lt;code&gt;finally&lt;/code&gt; block. The snapshot carries references to context objects; it is not a deep copy that makes arbitrary shared mutations safe. Ordinary payload handlers receive their dependencies through injection; this lower-level API matters when implementing runtime integrations.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/semitexa/semitexa-core/blob/17861784cb29ea1092b97001024f495200465e71/src/Support/CoroutineLocal.php" rel="noopener noreferrer"&gt;CoroutineLocal implementation&lt;/a&gt; also provides a process-local fallback for CLI execution. Request lifecycle cleanup remains necessary there, where one process may perform several executions without Swoole coroutine teardown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reuse connections with clear ownership and bounded waiting
&lt;/h2&gt;

&lt;p&gt;A persistent runtime can keep useful database connections open. It also needs to prevent two executions from accidentally treating the same connection as their own transaction. Connection reuse becomes an ownership problem as well as a performance opportunity.&lt;/p&gt;

&lt;p&gt;Semitexa ORM's coroutine-aware pool uses a bounded Swoole channel. A caller borrows a connection, uses it and returns it. Pool acquisition has a timeout and reports exhaustion. The pool can recover abandoned borrows when a coroutine ends, and it tracks events such as discards and exhausted waits.&lt;/p&gt;

&lt;p&gt;That gives the application an explicit capacity boundary. When available connections are occupied, callers wait within a limit instead of creating an unbounded number of connections. Pool capacity is per pool in a worker process; size the deployment against the total number of workers and the database's connection budget.&lt;/p&gt;

&lt;p&gt;Returning a connection includes transaction hygiene. If PDO reports an open transaction, the pool attempts a rollback before reuse. If that cleanup fails, the connection is discarded. This prevents a later borrower from inheriting an unfinished transaction on that path. The check relies on PDO's transaction tracking, so raw SQL transaction commands are not a substitute for the managed transaction API.&lt;/p&gt;

&lt;p&gt;The implementation also recognizes process changes so inherited connection resources are not casually reused after a fork. These details matter because the resource outlives any single request.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/semitexa/semitexa-orm/blob/94163047fa3ed57bef05e3bce5b05804e490957a/src/Adapter/ConnectionPool.php" rel="noopener noreferrer"&gt;ORM ConnectionPool source&lt;/a&gt; shows the checkout, return, timeout and reclaim paths. The application-level lesson is straightforward: keep borrowed resources close to the work that uses them, release them promptly, and avoid holding a database connection while waiting on an unrelated remote service.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same runtime can deliver the page and keep it alive
&lt;/h2&gt;

&lt;p&gt;The dashboard from the opening example has several useful moments to render. Order details are ready now. Recommendations arrive later. Fulfillment progress changes while the visitor is watching. Semitexa's rendering model can represent each moment without relocating the business rules into the browser.&lt;/p&gt;

&lt;p&gt;A PHP handler fills a resource and Twig renders the initial HTML. A deferred region can arrive when its slower work finishes. SSE can carry subsequent updates. Coroutines let supported I/O waits yield while the worker handles other work; the connection and its resources still have a cost that the deployment must budget.&lt;/p&gt;

&lt;p&gt;This combination is a practical runtime strength: reusable application services, execution context and server rendering participate in one flow. A shipping rule can remain in PHP while its result appears in the first response, a deferred block or a later live update.&lt;/p&gt;

&lt;p&gt;Our &lt;a href="https://semitexa.com/blog/server-side-rendering-2-0" rel="noopener noreferrer"&gt;Server Side Rendering 2.0 guide&lt;/a&gt; contains a working shared-template example. The &lt;a href="https://semitexa.com/blog/streaming-sse-semitexa" rel="noopener noreferrer"&gt;Streaming SSE guide&lt;/a&gt; explores live delivery. Read them as concrete applications of the lifetime and concurrency model described here.&lt;/p&gt;

&lt;p&gt;A live connection also crosses operational boundaries. Proxies need suitable buffering and timeout settings. Clients need reconnection behavior. Authentication and tenant isolation still apply to streamed content. Semitexa provides framework mechanisms for live delivery; a deployment must configure the network path around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  A long-running worker must also know how to stop
&lt;/h2&gt;

&lt;p&gt;Startup is only part of a persistent runtime. A deployment eventually replaces workers. Timers may still be firing, an SSE connection may be open, and a coroutine may be waiting on a socket. A complete lifecycle has to account for that work.&lt;/p&gt;

&lt;p&gt;Semitexa exposes server lifecycle phases around startup and shutdown. Its current Swoole configuration enables asynchronous reload with a finite drain window. On worker exit, the bootstrap raises a drain signal, clears timers and attempts to cancel parked coroutines. It reports coroutines that refuse cancellation so a stalled shutdown has evidence.&lt;/p&gt;

&lt;p&gt;This is bounded shutdown, not a promise that every in-flight operation completes. Some driver calls cannot be interrupted immediately, and Swoole may force termination after the configured wait. Work that must survive a worker replacement needs durable job handling, appropriate retries and idempotency.&lt;/p&gt;

&lt;p&gt;The same ownership principle appears at smaller boundaries. HTTP handling resets registered per-request state and disposes request-scoped bindings in cleanup. Queue workers perform per-message cleanup. Scheduled execution restores a previous tenant context after a tenant-bound run. A process can stay alive while the framework closes an individual unit of work.&lt;/p&gt;

&lt;p&gt;These mechanisms are visible in &lt;a href="https://github.com/semitexa/semitexa-core/blob/17861784cb29ea1092b97001024f495200465e71/src/Server/SwooleBootstrap.php" rel="noopener noreferrer"&gt;SwooleBootstrap&lt;/a&gt; and its &lt;a href="https://github.com/semitexa/semitexa-core/blob/17861784cb29ea1092b97001024f495200465e71/src/Server/ServerConfigurator.php" rel="noopener noreferrer"&gt;server configuration&lt;/a&gt;. For application developers, they provide explicit places to attach lifecycle behavior and explain why an unbounded background loop needs an exit strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect the behavior and make the boundary executable
&lt;/h2&gt;

&lt;p&gt;Long-running behavior becomes easier to trust when you can examine more than a successful first response. Semitexa's development tooling connects route structure, active processes and recorded execution. The tools operate on the same application whose lifecycle you are investigating.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Terminal · inspect the article route and a source-linked development trace&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/semitexa ai:ask route &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/blog/long-running-php-semitexa &lt;span class="nt"&gt;--json&lt;/span&gt;

bin/semitexa ai:observe ps

&lt;span class="c"&gt;# In development, visit the article with ?__trace=1 first.&lt;/span&gt;
bin/semitexa ai:observe &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;BlogArticle &lt;span class="nt"&gt;--lines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="nt"&gt;--json&lt;/span&gt;

&lt;span class="c"&gt;# Replace p-YOUR-ID with the request id from the journal.&lt;/span&gt;
bin/semitexa ai:observe show &lt;span class="nt"&gt;--id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;p-YOUR-ID &lt;span class="nt"&gt;--source&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Route inspection identifies the payload, handler, resource and template. Observatory lets you inspect process lifecycles and, for a traced development request, the recorded handler execution. Full traces and source views in this walkthrough require development mode.&lt;/p&gt;

&lt;p&gt;The stronger isolation check is an interleaving test. Two coroutines install different request contexts, both yield, and then each resolves a context-dependent object. Each must still receive its own request. The core test suite also checks that a child starts without the parent's context and that explicit propagation restores the intended values.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Terminal · run the actual context isolation and lifecycle regression tests&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run from the Semitexa development workspace with its Swoole runtime.&lt;/span&gt;
bin/semitexa &lt;span class="nb"&gt;test&lt;/span&gt;:run &lt;span class="se"&gt;\&lt;/span&gt;
  packages/semitexa-core/tests/Unit/Container/SemitexaContainerExecutionContextIsolationTest.php

bin/semitexa &lt;span class="nb"&gt;test&lt;/span&gt;:run &lt;span class="se"&gt;\&lt;/span&gt;
  packages/semitexa-core/tests/Integration/RequestScopedContainerLifecycleTest.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands target tests in the development workspace; an installed distribution may not include that test tree. Coroutine cases require the Swoole extension and are skipped without it. Check the result for skipped tests as well as failures. The &lt;a href="https://github.com/semitexa/semitexa-core/blob/17861784cb29ea1092b97001024f495200465e71/tests/Unit/Container/SemitexaContainerExecutionContextIsolationTest.php" rel="noopener noreferrer"&gt;concurrent isolation test source&lt;/a&gt; shows exactly which boundary is exercised.&lt;/p&gt;

&lt;p&gt;Performance needs its own evidence. Compare cold and warm behavior on the same workload, then measure latency under concurrency, memory over sustained traffic, database wait time and failure recovery. Avoid inferring an application-wide speedup from a counter or a hello-world route. Semitexa supplies mechanisms that remove repeated work and manage concurrency; the gain depends on where your application spends time.&lt;/p&gt;

&lt;p&gt;When editing a development checkout, reload the running application before testing changed server code: &lt;code&gt;bin/semitexa server:restart app&lt;/code&gt;. CLI checks such as &lt;code&gt;ai:verify&lt;/code&gt; run in fresh processes and do not need that restart. That distinction is another direct consequence of the worker lifetime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build around the lifetimes the application actually has
&lt;/h2&gt;

&lt;p&gt;Semitexa's runtime design gives a PHP application a coherent set of building blocks: a warm service graph, execution-scoped handlers, coroutine-local context, bounded connection reuse, live server-rendered output and explicit worker shutdown. Observability and regression tests make those boundaries inspectable.&lt;/p&gt;

&lt;p&gt;That is the significance of designing for long-running PHP from day one. The execution model shapes how dependencies are declared, how a handler is created, how state is carried, how a connection is returned and how HTML reaches the browser. Each part has a defined place in the application lifetime.&lt;/p&gt;

&lt;p&gt;Start with one real feature. Keep stable services reusable, put execution-specific data in the appropriate scope, keep resource borrowing short, and test the second request as carefully as the first. Then add the overlapping requests and live updates that make the runtime's strengths useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a PHP application that stays ready.
&lt;/h2&gt;

&lt;p&gt;Install Semitexa, inspect one request, and follow its state from the worker to the rendered page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://framework.semitexa.com/demo/get-started/installation" rel="noopener noreferrer"&gt;Get started with Semitexa →&lt;/a&gt; &lt;a href="https://semitexa.com/blog/long-running-php-semitexa#runtime-lab" rel="noopener noreferrer"&gt;Run the lifecycle example again&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Implementation references are pinned to the core and ORM revisions inspected for this article. Runtime details and command options can evolve; consult your installed version's help and source when applying an example.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was first published on the &lt;a href="https://semitexa.com/blog/long-running-php-semitexa" rel="noopener noreferrer"&gt;Semitexa blog&lt;/a&gt;, where the live demos run next to the text. Semitexa is on &lt;a href="https://github.com/semitexa" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>swoole</category>
      <category>performance</category>
      <category>backend</category>
    </item>
    <item>
      <title>AI-Native PHP Development with Semitexa</title>
      <dc:creator>Taras Hanych</dc:creator>
      <pubDate>Mon, 28 Sep 2026 10:38:33 +0000</pubDate>
      <link>https://dev.to/semitexa/ai-native-php-development-with-semitexa-5bf5</link>
      <guid>https://dev.to/semitexa/ai-native-php-development-with-semitexa-5bf5</guid>
      <description>&lt;p&gt;&lt;em&gt;An AI coding agent can produce a convincing patch. The harder question is whether it found the right code, understood the rule, and proved that the application now behaves correctly. AI-native PHP development with Semitexa gives that investigation a concrete foundation: an explicit execution path, a queryable project graph, observable requests, and verification the agent can run.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-native PHP starts with an application an agent can inspect
&lt;/h2&gt;

&lt;p&gt;A developer reports: “Free shipping is broken.” Before changing a line, an agent has several questions to answer. Which route handles the request? Which policy calculates shipping? Is the browser displaying a server result or calculating its own? What happened for the failing order? Does “from $100” include $100 itself?&lt;/p&gt;

&lt;p&gt;A repository can contain all the answers and still make them expensive to find. A string search returns references with different meanings. A class name suggests responsibility but does not prove it. A screenshot shows the wrong total without identifying the calculation. Even a successful request tells us only that execution completed.&lt;/p&gt;

&lt;p&gt;Semitexa gives those questions named tools. Route introspection explains the request chain. Project Graph reports structural relationships. Observatory records execution. The agent can move from a user-visible symptom to a specific method with evidence at each step.&lt;/p&gt;

&lt;p&gt;The roles remain clear: &lt;strong&gt;the agent reasons; Semitexa supplies an execution, inspection, memory, and verification environment.&lt;/strong&gt; The framework does not decide the product requirement. The developer still owns the acceptance criteria and reviews the change. “AI-native” here means that an agent has useful, structured ways to discover and check the application it is editing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;th&gt;Question it answers&lt;/th&gt;
&lt;th&gt;What it cannot establish alone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Route introspection and Project Graph&lt;/td&gt;
&lt;td&gt;How is this feature connected?&lt;/td&gt;
&lt;td&gt;Which path a particular request took&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime trace&lt;/td&gt;
&lt;td&gt;Which instrumented steps actually ran?&lt;/td&gt;
&lt;td&gt;Whether the business result was correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Acceptance cases and tests&lt;/td&gt;
&lt;td&gt;Did the result meet the stated rule?&lt;/td&gt;
&lt;td&gt;Every possible input or deployment condition&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Reproduce the bug: one cent makes the difference
&lt;/h2&gt;

&lt;p&gt;Our requirement is deliberately small and unambiguous: &lt;strong&gt;standard delivery costs $12 below a $100 subtotal, and is free at $100 or more.&lt;/strong&gt; There are no taxes, discounts, currencies to convert, or regional exceptions in this lab. The amounts are represented as integer cents.&lt;/p&gt;

&lt;p&gt;Start with the faulty fixture at $100.00. Then try $99.99 and $100.01. Both neighboring cases behave correctly, which is why a quick check of an ordinary order could miss the defect. Finally, choose the corrected rule and repeat all three cases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://semitexa.com/blog/ai-native-php-development-semitexa#shipping-lab" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;▶ Run the live demo — Free standard delivery from $100, inclusive&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;The controls submit a regular GET form. Semitexa hydrates a typed payload, runs the handler, and renders the result through Twig. The example also works with JavaScript disabled. The browser displays the amounts returned by PHP; it contains no second shipping calculation.&lt;/p&gt;

&lt;p&gt;This is a controlled teaching fixture with two predefined implementations. Selecting “corrected rule” does not ask an AI model to generate a patch or edit a file. Keeping the faulty implementation available lets every reader reproduce the same mistake and compare it with the correct behavior.&lt;/p&gt;

&lt;p&gt;The defect is a single comparison. In the faulty branch of &lt;code&gt;DemoShippingRuleLab&lt;/code&gt;, the condition is:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PHP · the faulty fixture excludes the threshold itself&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$subtotalCents&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finding that line is easy once someone tells you where it is. The useful engineering problem is reaching it from the symptom without guessing, then demonstrating that the correction satisfies the requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find the route before searching the whole repository
&lt;/h2&gt;

&lt;p&gt;In a local Semitexa development checkout containing this Demo article, ask the framework for the route chain:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Terminal · route ownership and the payload's graph impact&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/semitexa ai:ask route &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/blog/ai-native-php-development-semitexa &lt;span class="nt"&gt;--json&lt;/span&gt;

bin/semitexa ai:review-graph:impact &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'Semitexa\Demo\Application\Payload\Request\BlogAiNativePayload'&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The route query identifies a public GET endpoint, &lt;code&gt;BlogAiNativePayload&lt;/code&gt;, its synchronous &lt;code&gt;BlogAiNativeHandler&lt;/code&gt;, &lt;code&gt;BlogAiNativeResource&lt;/code&gt;, and &lt;code&gt;pages/blog-ai-native.html.twig&lt;/code&gt;. The graph impact query reports the handler one relationship away from the payload. These are results from the implementation of this article.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Typed payload       → handler    PHP lab service
Calculated result   → resource   Twig → HTML
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The article's code path, combining route metadata with the handler source. This diagram is an explanation, not a live trace visualization.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now the investigation has a narrow starting point. The handler's source shows its injected &lt;code&gt;DemoShippingRuleLab&lt;/code&gt; service and the call to &lt;code&gt;run()&lt;/code&gt;. That is the policy to inspect; the template is responsible for displaying its result.&lt;/p&gt;

&lt;p&gt;Graph results have a scope. In the inspected build, the handler dependency query exposed its typed payload, resource, and interface relationships, but did not list the injected lab service. Reading the identified handler supplied that last connection. An agent should treat the graph as evidence about the edges it reports, rather than assume that an absent edge proves there is no dependency.&lt;/p&gt;

&lt;p&gt;This becomes especially useful when a feature spans modules. Start from an actual route or symbol, follow the relevant relationships, and open the files that answer the remaining question. The &lt;a href="https://semitexa.com/blog/project-graph-semitexa" rel="noopener noreferrer"&gt;Project Graph guide&lt;/a&gt; explores that structural workflow in more detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observe the request that produced the wrong answer
&lt;/h2&gt;

&lt;p&gt;Structure tells us where to look. Runtime evidence tells us whether the request reached that code. In your local development environment, open this article's route with the trace flag:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Development URL · request a full trace for the failing case&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/blog/ai-native-php-development-semitexa?scenario=boundary&amp;amp;rule=buggy&amp;amp;__trace=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the process journal to find the request, then inspect its source-linked trace. The request name is the payload class name; the process ID comes from the journal:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Terminal · inspect the recorded execution and its source&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/semitexa ai:observe &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;BlogAiNative &lt;span class="nt"&gt;--lines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="nt"&gt;--json&lt;/span&gt;

&lt;span class="c"&gt;# Replace p-YOUR-ID with the completed request's id.&lt;/span&gt;
bin/semitexa ai:observe show &lt;span class="nt"&gt;--id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;p-YOUR-ID &lt;span class="nt"&gt;--source&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request used while preparing this guide returned HTTP 200 and recorded a &lt;code&gt;pipeline.handler&lt;/code&gt; span whose &lt;code&gt;source_ref&lt;/code&gt; was &lt;code&gt;BlogAiNativeHandler::handle&lt;/code&gt;, with its full namespace. The source output included the actual call that passes the selected scenario and rule to the lab service.&lt;/p&gt;

&lt;p&gt;Notice the distinction: the request completed successfully while displaying a wrong business result. HTTP 200 is not a shipping-policy test. Likewise, the handler span proves that the instrumented handler ran; it does not imply a separate span exists for every internal PHP method call.&lt;/p&gt;

&lt;p&gt;A developer can inspect the same development environment through &lt;code&gt;/__observatory&lt;/code&gt; and the waterfall at &lt;code&gt;/__trace&lt;/code&gt;. Source views connect recorded steps to code without a second search through similarly named handlers. Those source slices are read from the current files, so they are most useful before the implementation changes; they are not an immutable historical copy of the code.&lt;/p&gt;

&lt;p&gt;Full traces, source inspection, and replay in this walkthrough require development mode. Observatory also has a restricted production monitoring mode for lifecycle records, with different access and data limits. The public lab on this page exposes only its fixed shipping cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Correct the rule where the rule lives
&lt;/h2&gt;

&lt;p&gt;The acceptance criterion includes the threshold itself. The corrected PHP expression is therefore:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PHP · free delivery includes an exact $100 subtotal&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$subtotalCents&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a real shipping policy, that is the implementation we would keep. This article retains both branches in the isolated lab so the comparison remains runnable. The expected amounts are stored separately as explicit acceptance cases; they are not calculated by calling the same shipping function and trusting its answer twice.&lt;/p&gt;

&lt;p&gt;The handler coordinates the work and passes decided values to the response resource. This is the relevant call from the page's real handler:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PHP · handler excerpt, expanded for readability&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withLab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;shippingLab&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getScenario&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRule&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twig owns the view: labels, amounts, selected options, and the visible comparison. The service owns the calculation. This separation reduces the number of places an agent must change and the number of competing implementations a reviewer must reconcile.&lt;/p&gt;

&lt;p&gt;The same ownership principle matters when interfaces become more dynamic. A deferred region can receive a server-calculated result, and a live update can deliver newly rendered content without inventing a browser-side shipping policy. Our &lt;a href="https://semitexa.com/blog/server-side-rendering-2-0" rel="noopener noreferrer"&gt;Server Side Rendering 2.0 article&lt;/a&gt; demonstrates shared templates and deferred blocks; the &lt;a href="https://semitexa.com/blog/streaming-sse-semitexa" rel="noopener noreferrer"&gt;Streaming SSE guide&lt;/a&gt; follows live delivery.&lt;/p&gt;

&lt;p&gt;One source of truth has a precise meaning here: &lt;strong&gt;one owner for the business rule, and one authoritative template for the view.&lt;/strong&gt; Moving the comparison into Twig would blur that boundary again. Asking an agent to keep duplicated PHP and JavaScript pricing functions synchronized would preserve the duplication rather than resolve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replay the handler with an explicit change of input
&lt;/h2&gt;

&lt;p&gt;Once a request has a full trace, Semitexa can use it as the starting point for a handler replay. For this isolated lab, provide both inputs explicitly and select the already implemented corrected branch:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Terminal · re-execute the recorded handler with explicit lab inputs&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/semitexa ai:observe replay &lt;span class="nt"&gt;--id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;p-YOUR-ID &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mutate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;scenario&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;boundary &lt;span class="nt"&gt;--mutate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;rule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fixed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The replay we ran returned &lt;code&gt;verdict: ok&lt;/code&gt;. Its resource contained the following lab values, shown here as a shortened selection from the output:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Observed replay output · selected lab fields, not the full envelope&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scenario"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boundary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fixed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actualShippingCents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expectedShippingCents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actualTotal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$100.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explicit inputs matter. This page's original hydration trace contained an empty payload snapshot, so it would be incorrect to claim that replay automatically preserved the selected form values. Here we know exactly which scenario we are rerunning because the command supplies it.&lt;/p&gt;

&lt;p&gt;Replay also has a narrower execution boundary than a fresh browser request: it invokes the resolved handler with a hydrated payload and resource. It does not reproduce the complete HTTP, authorization, and rendering pipeline. Use it to inspect a handler result, then exercise the actual page to verify delivery and presentation.&lt;/p&gt;

&lt;p&gt;The replay runner rolls back its managed database transaction and captures queue handoffs. Supported mail transport is withheld during the sandboxed run. Arbitrary outbound HTTP calls and LLM provider calls are not universally suppressed, so replay is not a blanket guarantee of external isolation. This lab performs no such calls and creates no orders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the acceptance criterion executable
&lt;/h2&gt;

&lt;p&gt;A plausible patch becomes a useful fix when the requirement survives independent checks. For our inclusive threshold, the three adjacent cases provide a compact specification:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Subtotal&lt;/th&gt;
&lt;th&gt;Expected delivery&lt;/th&gt;
&lt;th&gt;Faulty rule&lt;/th&gt;
&lt;th&gt;Corrected rule&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;$99.99&lt;/td&gt;
&lt;td&gt;$12.00&lt;/td&gt;
&lt;td&gt;$12.00 · matches&lt;/td&gt;
&lt;td&gt;$12.00 · matches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$100.00&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;$12.00 · mismatch&lt;/td&gt;
&lt;td&gt;$0.00 · matches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$100.01&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;$0.00 · matches&lt;/td&gt;
&lt;td&gt;$0.00 · matches&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The article's test suite checks all six scenario/implementation combinations and the fallback for unknown inputs: seven tests and 25 assertions. A test for the faulty branch asserts that the lab exposes the mismatch; it does not approve that charge as correct product behavior.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Terminal · execute the acceptance cases and scoped framework checks&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/semitexa &lt;span class="nb"&gt;test&lt;/span&gt;:run &lt;span class="se"&gt;\&lt;/span&gt;
  packages/semitexa-demo/tests/Unit/Service/DemoShippingRuleLabTest.php

bin/semitexa ai:verify &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;packages/semitexa-demo/src/Application/Service/DemoShippingRuleLab.php &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;packages/semitexa-demo/tests/Unit/Service/DemoShippingRuleLabTest.php &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ai:verify&lt;/code&gt; selects applicable checks for the files under review. Its report is evidence about the checks it ran. The focused service tests establish the boundary behavior; a browser check additionally establishes that form inputs reach PHP, values render correctly, and the page remains usable.&lt;/p&gt;

&lt;p&gt;Semitexa's long-running workers cache discovered classes and compiled templates. After changing the implementation, run &lt;code&gt;bin/semitexa server:restart&lt;/code&gt; before testing the running HTTP server. CLI verification uses fresh processes and does not need that restart.&lt;/p&gt;

&lt;p&gt;The product decision still comes first. If the requirement had said “strictly over $100,” the original comparison would be correct. An agent must resolve that ambiguity with the product owner or an authoritative specification. Neither a graph nor a trace can invent the intended meaning of a promotion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the evidence when the conversation moves on
&lt;/h2&gt;

&lt;p&gt;Long investigations often fail at the handoff. The next session sees a modified file but loses the reason for the change, the failing input, and the hypothesis that was already disproved. The agent spends time reconstructing an investigation someone already completed.&lt;/p&gt;

&lt;p&gt;Semitexa's &lt;code&gt;ai:epic&lt;/code&gt;, &lt;code&gt;ai:work&lt;/code&gt;, and &lt;code&gt;ai:trace&lt;/code&gt; commands give ongoing work durable artifacts. &lt;code&gt;ai:orient&lt;/code&gt; brings the active work and recent verification back into view. &lt;code&gt;ai:context&lt;/code&gt; retrieves relevant prior context before an agent starts reading from scratch.&lt;/p&gt;

&lt;p&gt;For this example, a useful handoff would preserve the inclusive-$100 requirement, the $112 failing result, the identified handler and service, the empty trace snapshot limitation, and the passing acceptance cases. “Investigated shipping” would preserve almost nothing.&lt;/p&gt;

&lt;p&gt;The amount of process should fit the work. A focused correction can stay small. A change spanning several modules needs explicit tasks, dependencies, decisions, and a next step that another session can actually execute. The purpose is continuity: preserve the facts needed to continue without asking an agent to remember a conversation forever.&lt;/p&gt;

&lt;p&gt;Evidence also needs freshness. A graph, a source view, or a passing test report describes a particular state of the project. After editing relevant code, rerun the checks that could have changed and record the new result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start AI-native PHP development with one real bug
&lt;/h2&gt;

&lt;p&gt;The shipping mistake is small enough to understand in one sitting. The workflow scales because each step asks a specific question: reproduce the symptom, locate the route, inspect the relevant structure, observe execution, correct the owning rule, and verify the requirement.&lt;/p&gt;

&lt;p&gt;Semitexa makes that sequence concrete through the application itself. An agent can query framework metadata, follow a source-linked runtime step, execute a focused test, and leave the evidence for the next session. A developer can review those same artifacts and challenge a conclusion at the point where it was made.&lt;/p&gt;

&lt;p&gt;That is the useful promise of AI-native PHP development: a shorter, more inspectable path from “this behavior is wrong” to “here is the rule, the change, and the evidence that it now holds.” The strongest demonstration is an application you can run and question.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try it on your own feature.&lt;/strong&gt; Choose one reproducible request and write its expected result first. Start with &lt;code&gt;bin/semitexa ai:orient --json&lt;/code&gt;, then ask for its route chain. Keep the investigation narrow enough that every proposed change can be connected to a concrete acceptance case.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Continue with the &lt;a href="https://framework.semitexa.com/demo/get-started/installation" rel="noopener noreferrer"&gt;Semitexa installation guide&lt;/a&gt;, explore the &lt;a href="https://github.com/semitexa/semitexa-dev" rel="noopener noreferrer"&gt;development tooling package&lt;/a&gt;, or return to the &lt;a href="https://semitexa.com/blog/ai-native-php-development-semitexa#shipping-lab" rel="noopener noreferrer"&gt;shipping lab&lt;/a&gt; and test the boundary yourself. Commands in this guide reflect the development build used for the article; your installed version's &lt;code&gt;--help&lt;/code&gt; and capability output describe its available options.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was first published on the &lt;a href="https://semitexa.com/blog/ai-native-php-development-semitexa" rel="noopener noreferrer"&gt;Semitexa blog&lt;/a&gt;, where the live demos run next to the text. Semitexa is on &lt;a href="https://github.com/semitexa" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>ai</category>
      <category>testing</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Server Side Rendering 2.0</title>
      <dc:creator>Taras Hanych</dc:creator>
      <pubDate>Mon, 28 Sep 2026 10:38:11 +0000</pubDate>
      <link>https://dev.to/semitexa/server-side-rendering-20-kob</link>
      <guid>https://dev.to/semitexa/server-side-rendering-20-kob</guid>
      <description>&lt;p&gt;&lt;em&gt;PHP began with a remarkably direct idea: the server knows the data, so let it build the page. Decades of richer interfaces have taught us what that model needed next. Semitexa combines one authoritative template with deferred blocks, live delivery, and explicit PHP boundaries. This article is a working example of that architecture.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Before SSR had a fashionable name, PHP was rendering pages
&lt;/h2&gt;

&lt;p&gt;Server rendering is part of PHP's foundation. The &lt;a href="https://www.php.net/manual/en/history.php.php" rel="noopener noreferrer"&gt;PHP project's history&lt;/a&gt; traces its early development through form handling, database interaction, and syntax embedded in HTML. The request arrived at the server; PHP evaluated the dynamic parts; the browser received a document.&lt;/p&gt;

&lt;p&gt;A familiar template from a later generation of PHP applications looked like this. HTML supplied the structure, and a block object supplied the values:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Classic PHP template · HTML with PHP expressions&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"product-card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?=&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTitle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?=&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getDescription&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?=&lt;/span&gt; &lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getFormattedPrice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser never executes &lt;code&gt;$block-&amp;gt;getTitle()&lt;/code&gt;. PHP has already replaced that expression with output before the response arrives. The &lt;a href="https://www.php.net/manual/en/language.basic-syntax.phpmode.php" rel="noopener noreferrer"&gt;PHP manual describes this mixture of HTML and PHP&lt;/a&gt; directly. The block object is a familiar architectural example, not a claim that the earliest PHP versions used this class design.&lt;/p&gt;

&lt;p&gt;The short example shows the old shape of the code. For a title supplied as plain text, an actual application must also escape output for its HTML context:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PHP · escaping a plain-text title&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?=&lt;/span&gt; &lt;span class="nb"&gt;htmlspecialchars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$block&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getTitle&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="no"&gt;ENT_QUOTES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'UTF-8'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This model had a valuable property: one template described what the user would see. Links were links, form submissions reached the server, and the page contained useful content from the beginning. Its problems came from how applications were structured and updated. Templates could grow SQL queries and business decisions. A slow dependency could delay the entire response. Updating a small region often meant loading the whole page again.&lt;/p&gt;

&lt;p&gt;Those limitations were real. The useful lesson was to improve the boundaries and the delivery of the page while preserving the clarity of a single rendering definition.&lt;/p&gt;

&lt;h2&gt;
  
  
  We gained richer interfaces—and another application to keep correct
&lt;/h2&gt;

&lt;p&gt;Separate frontend applications solved important problems. Browser code could manage complex interactions, update local state, and provide experiences beyond a form followed by a full reload. APIs made it possible to serve multiple clients. Specialist teams could work and release independently.&lt;/p&gt;

&lt;p&gt;But a product feature rarely stops neatly at the API boundary. A new discount changes a server calculation, a preview total, an eligibility message, and a checkout button. What looked like one behavior becomes several tasks across repositories, languages, tests, and release schedules. The teams need a shared understanding of every state, including the states that an API document forgot to name.&lt;/p&gt;

&lt;p&gt;Some organizations manage this with two specialist teams and careful coordination. Others ask every developer to work confidently in both stacks. Neither arrangement removes the underlying cost: people still have to understand two execution environments and keep the same product behavior consistent across them. Smaller teams often feel that cost particularly sharply.&lt;/p&gt;

&lt;h3&gt;
  
  
  The expensive leak is a decision copied across the boundary
&lt;/h3&gt;

&lt;p&gt;Consider a $160 order with a 10% member discount and $12 express delivery. The backend calculates $156. A browser preview must display the same answer. If it reconstructs the discount rules from a customer flag and an item list, it has become another implementation of pricing. A change to the order of operations can produce two believable totals.&lt;/p&gt;

&lt;p&gt;The same leak appears when a client infers permission from a role label, invents a workflow status, or guesses whether a refund is allowed. Client validation can improve feedback, but the server still has to enforce the rule. A hidden button cannot authorize an operation. A plausible progress animation cannot establish that a job has finished.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The goal is a clear owner for each decision.&lt;/strong&gt; Keep pricing, permissions, and state transitions in application services. Pass their results to the view. Keep the view definition in one template. Transport should deliver those results without creating a competing model of the product.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The template is the single source of truth for the view
&lt;/h2&gt;

&lt;p&gt;In Semitexa, a Twig template can remain the authoritative definition of a region whether that region appears in the first HTML response or arrives later. You do not have to maintain a PHP view and a separate JavaScript component that reproduce the same markup, labels, conditions, and empty states.&lt;/p&gt;

&lt;p&gt;That statement has a precise scope. The template owns &lt;em&gt;presentation&lt;/em&gt;. A PHP service owns &lt;em&gt;business decisions&lt;/em&gt;. A repository or external system owns persisted facts. Putting every rule into Twig would recreate the coupling that early PHP applications struggled with. The benefit comes from giving each concern one clear home.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Authoritative place&lt;/th&gt;
&lt;th&gt;What the browser receives&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What does this order cost?&lt;/td&gt;
&lt;td&gt;The PHP quote policy&lt;/td&gt;
&lt;td&gt;The calculated amount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How should the quote appear?&lt;/td&gt;
&lt;td&gt;One Twig template&lt;/td&gt;
&lt;td&gt;HTML, or that published template and its data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When is a slow region ready?&lt;/td&gt;
&lt;td&gt;The server operation completing&lt;/td&gt;
&lt;td&gt;A deferred delivery frame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which carousel item is visible?&lt;/td&gt;
&lt;td&gt;A small browser interaction&lt;/td&gt;
&lt;td&gt;No second pricing or stock policy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is part of the actual quote template used by this page. Its inputs are already decided. The template describes the labels and where values appear:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Twig · one view reused by all three quote panels&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;quote.deliveryLabel&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dl&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&amp;lt;dt&amp;gt;&lt;/span&gt;Member discount&lt;span class="nt"&gt;&amp;lt;/dt&amp;gt;&amp;lt;dd&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;quote.discount&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/dd&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&amp;lt;dt&amp;gt;&lt;/span&gt;Delivery&lt;span class="nt"&gt;&amp;lt;/dt&amp;gt;&amp;lt;dd&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;quote.shipping&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/dd&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&amp;lt;dt&amp;gt;&lt;/span&gt;Total&lt;span class="nt"&gt;&amp;lt;/dt&amp;gt;&amp;lt;dd&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;quote.total&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/dd&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dl&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The quote above uses &lt;code&gt;partials/blog-ssr2-quote.html.twig&lt;/code&gt; for display and &lt;code&gt;BlogQuotePolicy&lt;/code&gt; for its calculation. The Framework demo uses its own shared view and &lt;code&gt;DemoSsrQuotePolicy&lt;/code&gt; for the deferred examples. In each case, the browser receives the server's decision rather than recalculating the discount.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server Side Rendering 2.0: keep the page, improve how it arrives
&lt;/h2&gt;

&lt;p&gt;“Server Side Rendering 2.0” is the architectural idea of this article, rather than a protocol version. Semitexa keeps the request-to-HTML path explicit: a typed payload represents input, a handler coordinates application services, a resource carries the result, and Twig renders the view.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request                   → typed payload + handler   PHP rules
Decided values            → resource + Twig           First HTML
Slow operation finishes   → deferred SSE delivery     Its region appears
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Business decisions remain on the server. A region can arrive later without acquiring a separate frontend implementation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One slow region no longer has to hold the whole document hostage. A deferred slot reserves space with a skeleton. Semitexa resolves that slot after the shell, then delivers its result. Under the coroutine runtime, independent work can proceed concurrently; a real blocking call or a shared bottleneck still needs attention. Deferring work changes when the page can become useful, not the amount of work a database must do.&lt;/p&gt;

&lt;p&gt;The quote below makes the immediate response visible. The linked Framework demos show how deferred regions and an interactive product rail arrive later, without moving the business rule into the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo 1: a PHP policy renders the first response
&lt;/h2&gt;

&lt;p&gt;Choose standard or express delivery. This ordinary GET form rerenders the quote on semitexa.com using the same PHP calculation and Twig template. For deferred delivery, open the live &lt;a href="https://framework.semitexa.com/demo/rendering/deferred" rel="noopener noreferrer"&gt;Framework demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://semitexa.com/blog/server-side-rendering-2-0#ssr2-demo" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;▶ Run the live demo — Member order · $160 subtotal · 10% discount&lt;/a&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  What each delivery path proves
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;First response:&lt;/strong&gt; the page handler calls the PHP policy and Twig renders the view on the server. &lt;strong&gt;Deferred HTML:&lt;/strong&gt; a slot handler can render a view later and send finished HTML over SSE. &lt;strong&gt;Deferred template:&lt;/strong&gt; the server can supply decided values and a reference to a published Twig template for the supported client renderer. Explore those latter paths in the &lt;a href="https://framework.semitexa.com/demo/rendering/deferred" rel="noopener noreferrer"&gt;Framework rendering demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The third path is especially useful when discussing a single source of truth. Browser rendering does not have to mean a second, independently maintained view. Semitexa's template mode reuses the declared Twig source. It supports a constrained template feature set, so complex server helpers belong in the PHP handler or in HTML mode. Values sent to this mode are visible to the client, just like any other browser payload.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PHP · the calculation used for the quote&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// DemoSsrQuotePolicy::quote() — amounts are integer cents.&lt;/span&gt;
&lt;span class="nv"&gt;$subtotalCents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$discountCents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;intdiv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$subtotalCents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$shippingCents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$delivery&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'express'&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$totalCents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$subtotalCents&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$discountCents&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$shippingCents&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a condensed excerpt of the demo policy; it also formats the amounts and supplies display labels. The page payload accepts the delivery choice, and the deferred handler reads that choice from the page context. In a real checkout, the server would load and authorize the order, calculate against current data, and revalidate on submission. Sharing a template does not freeze a changing business record in time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo 2: a deferred block can bring its own interaction
&lt;/h2&gt;

&lt;p&gt;The Semitexa deferred carousel has a PHP handler for product data and a Twig template for the cards. Once the block arrives, a small client module activates Prev and Next. Try it in the Framework demo: JavaScript selects what is visible, while the product values come from the server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://framework.semitexa.com/demo/rendering/deferred" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;▶ Open the deferred carousel demo&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;The fixture products are demonstration data, and the delay is intentional. Reload the Framework demo to watch its skeleton resolve and compare how regions finish independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  A small declaration describes the delivery contract
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;PHP · the HTML slot declaration, with its second registration omitted&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Semitexa\Ssr\Attribute\AsSlotResource&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Semitexa\Ssr\Application\Service\Http\Response\HtmlSlotResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;AsSlotResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'demo_blog_ssr2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'ssr2_receipt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'@project-layouts-semitexa-demo/partials/blog-ssr2-quote.html.twig'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deferred&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;skeletonTemplate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'@project-layouts-semitexa-demo/deferred/blog-ssr2-receipt.skeleton.html.twig'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BlogSsr2ReceiptSlot&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HtmlSlotResponse&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;withQuote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$quote&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;static&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'quote'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$quote&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the Framework demo, template delivery registers the same resource with a different slot name and &lt;code&gt;mode: 'template'&lt;/code&gt;. Both registrations name the same quote template. A discovered &lt;code&gt;#[AsSlotHandler]&lt;/code&gt; supplies the data. That page chooses where each slot appears:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Twig · place a deferred region&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Order quote"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;layout_slot_deferred&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ssr2_receipt'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful well beyond a loading animation. A product page can return its title and purchase context while recommendations wait on another service. A dashboard can reveal a quick summary while a slower report resolves. A component can bring a chart module that paints a canvas from server values. Each region has a declared template and lifecycle, instead of a bespoke fetch route plus another hand-written rendering function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deferred arrival, periodic refresh, and events are different jobs
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;Good example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deferred slot&lt;/td&gt;
&lt;td&gt;The region finishes preparing&lt;/td&gt;
&lt;td&gt;A recommendation service returns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;refreshInterval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A server refresh interval elapses on a permitted persistent stream&lt;/td&gt;
&lt;td&gt;A metrics snapshot checked periodically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server event&lt;/td&gt;
&lt;td&gt;The server emits a change notification&lt;/td&gt;
&lt;td&gt;A background task reports completion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A deferred declaration does not automatically subscribe a block to every domain event. Live updates need an explicit publication and subscription path, or an explicitly configured refresh interval. Making that distinction keeps the architecture understandable: “render this later” and “keep this fresh” are related capabilities with different lifecycles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo 3: the server changes, and the browser hears about it
&lt;/h2&gt;

&lt;p&gt;Server rendering can stay live after the initial document has arrived. An SSE connection remains open so the server can send an event when it is produced. The browser does not have to ask every few seconds whether something happened. Delivery still takes processing and network time; “immediate” means pushed after emission, without waiting for the browser's next polling cycle.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://framework.semitexa.com/demo/events/sse" rel="noopener noreferrer"&gt;Semitexa SSE showcase&lt;/a&gt; lets you sign in, connect, and watch a server notification arrive. A new &lt;code&gt;scheduler.tick&lt;/code&gt; follows at each server minute boundary when the debug producer is enabled. Its timestamp is produced on the backend; the visible countdown only predicts the next tick.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://framework.semitexa.com/demo/events/sse" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;▶ Open the live SSE showcase&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;This optional persistent stream requires sign-in and the demo producer requires debug mode. It is separate from the short deferred delivery shown in the rendering demo.&lt;/p&gt;

&lt;h3&gt;
  
  
  The event carries the server's answer
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;PHP · illustrative application event using the installed SSE delivery API&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Semitexa\Ssr\Application\Service\Async\SseAsyncResultDelivery&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Inside a server-side producer, for an authorized subscriber session.&lt;/span&gt;
&lt;span class="nc"&gt;SseAsyncResultDelivery&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;deliverRaw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'event'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'report.ready'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'reportId'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$reportId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'sent_at'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;gmdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DATE_ATOM&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;$sessionId&lt;/code&gt; must come from the application's authorized subscriber mapping, and &lt;code&gt;$reportId&lt;/code&gt; from completed server work. The snippet illustrates publication; it does not establish subscriptions or authorize a report. The built-in panel uses its own &lt;code&gt;notification&lt;/code&gt; and &lt;code&gt;scheduler.tick&lt;/code&gt; events so you can inspect a working producer.&lt;/p&gt;

&lt;p&gt;For an application, the next step depends on the UI contract. A notification can consume the event fields. A resource response can be rendered on the server and delivered with its HTML through Semitexa's asynchronous result delivery. A subscribed collection can react to a scope invalidation and obtain an updated server projection. In each case, the event announces a fact already decided by the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job finishes                    → persist result + publish   Server event
Application presentation path   → the declared template      Updated view
SSE delivery                    → browser applies result     Visible change
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Wire the application's event-to-view path explicitly. The browser can show a completed report without independently deciding when a report counts as complete.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That is the bigger opportunity behind deferred blocks and SSE. A useful page can appear early, a slow block can join it later, and subsequent server activity can reach an already open page. The delivery mechanism evolves while the business rule and the authored view retain clear owners.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI agents benefit from an architecture with fewer conflicting answers
&lt;/h2&gt;

&lt;p&gt;An AI agent can generate a second price function very quickly. It can also produce a perfectly plausible patch to the wrong one. When a PHP calculation, a TypeScript preview, a server template, and a browser component all describe one checkout, the agent must discover every relationship before changing the behavior safely. Missing one is enough to create drift.&lt;/p&gt;

&lt;p&gt;Now give the same task to an agent working on this article's demo: “change the member discount to 15%, and rename the delivery label.” The calculation has one home in &lt;code&gt;DemoSsrQuotePolicy&lt;/code&gt;. The shared view has one home in &lt;code&gt;blog-ssr2-quote.html.twig&lt;/code&gt;. The first response, deferred HTML, and template delivery can be compared against the same expected result. The task becomes easier to locate, explain, and verify.&lt;/p&gt;

&lt;p&gt;This also improves collaboration between people. A designer can work on the Twig markup. A PHP developer can change the policy. A browser specialist can improve carousel behavior or chart accessibility without taking ownership of order calculations. Specialists still matter; the architecture makes their responsibilities easier to join.&lt;/p&gt;

&lt;p&gt;Semitexa's typed payloads, handlers, resources, and declared slots expose these relationships to its inspection tools. The &lt;a href="https://semitexa.com/blog/project-graph-semitexa" rel="noopener noreferrer"&gt;Project Graph&lt;/a&gt; helps an agent trace dependencies and assess impact before editing. Structural clarity narrows the search; verification still has to prove the outcome.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One template is an architectural advantage, not an automatic correctness guarantee.&lt;/strong&gt; It removes a duplicated view definition. Keeping business decisions in services removes a duplicated rule implementation. Together, those choices make both human and agent changes easier to reason about.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Use the delivery mode that the region actually needs
&lt;/h2&gt;

&lt;p&gt;Render inexpensive, essential content in the first response. Defer a region when waiting for it would materially delay the page. Keep a stream open when the product needs updates after arrival. A fast label does not improve by becoming a skeleton, and a static paragraph does not need a persistent connection.&lt;/p&gt;

&lt;p&gt;For production, make the slow operation bounded, keep placeholders stable, and verify that a failed region can recover without breaking the rest of the page. Check proxy buffering for SSE, decide what reconnect means for your data, and authorize both the initial content and subsequent updates. A cached fragment must respect the user and tenant boundaries of the values it contains.&lt;/p&gt;

&lt;p&gt;Test the paths your product promises: first HTML, successful deferred delivery, transport failure, crawler rendering, and JavaScript disabled. This page deliberately keeps the first quote and its form useful without client code. That does not mean an unresolved deferred region magically becomes live without a runtime.&lt;/p&gt;

&lt;p&gt;Commerce pages, account screens, forms, content, and operational tools often benefit from this model because the server already owns their important decisions. Rich offline editors, graphics applications, and heavily local interactions can justify a larger client application. Semitexa lets a server-rendered product add live behavior region by region.&lt;/p&gt;

&lt;p&gt;The original PHP strength survives: one understandable path from a request to a page. The new capability is that the page can arrive in stages and continue responding to server activity, with one template for its view and one authoritative implementation of its rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  One view definition. A page that keeps moving.
&lt;/h2&gt;

&lt;p&gt;Explore more deferred regions, inspect streaming SSE, or build your first Semitexa page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://framework.semitexa.com/demo/rendering/deferred" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Explore deferred blocks&lt;/a&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://semitexa.com/blog/streaming-sse-semitexa" rel="noopener noreferrer"&gt;Read the Streaming SSE guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://framework.semitexa.com/demo/get-started/installation" rel="noopener noreferrer"&gt;Start with Semitexa&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article was first published on the &lt;a href="https://semitexa.com/blog/server-side-rendering-2-0" rel="noopener noreferrer"&gt;Semitexa blog&lt;/a&gt;, where the live demos run next to the text. Semitexa is on &lt;a href="https://github.com/semitexa" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>ssr</category>
    </item>
    <item>
      <title>Project Graph in Semitexa: Map PHP Dependencies Before You Edit</title>
      <dc:creator>Taras Hanych</dc:creator>
      <pubDate>Mon, 28 Sep 2026 10:37:45 +0000</pubDate>
      <link>https://dev.to/semitexa/project-graph-in-semitexa-map-php-dependencies-before-you-edit-46ad</link>
      <guid>https://dev.to/semitexa/project-graph-in-semitexa-map-php-dependencies-before-you-edit-46ad</guid>
      <description>&lt;p&gt;&lt;em&gt;A change to one PHP payload looks small. Which handler consumes it? Which resource does that handler return? Which modules might feel the effect? Project Graph turns those questions into queries against the codebase's structure.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a project graph?
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;project graph&lt;/strong&gt; is a map of software entities and the relationships between them. Classes, routes, handlers, services, events, and modules are nodes. Relationships such as “handles this payload,” “implements this interface,” or “depends on this class” are directed edges. Unlike a list of text matches, the graph can answer structural questions: what uses this type, what does it depend on, and what could a change affect?&lt;/p&gt;

&lt;p&gt;That is useful when a repository is larger than one person's working memory. A developer can find an entry point before editing; a reviewer can inspect downstream impact; an AI assistant can receive a focused architecture slice rather than a large collection of loosely related files.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Use the graph for structure.&lt;/strong&gt; Keep using source search when the question is about exact text or a local implementation detail. The graph is most valuable when the question crosses files or modules.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Semitexa Project Graph actually records
&lt;/h2&gt;

&lt;p&gt;The optional &lt;code&gt;semitexa-project-graph&lt;/code&gt; package scans PHP source and Semitexa attributes, extracts structural facts, and stores a directed graph. Its nodes include routes, payloads, handlers, services, events, and execution flows. Its edges capture relationships such as &lt;code&gt;handles&lt;/code&gt;, &lt;code&gt;returns&lt;/code&gt;, &lt;code&gt;implements&lt;/code&gt;, and cross-module dependencies. The stored graph powers several different questions without making each developer reconstruct the architecture from scratch.&lt;/p&gt;

&lt;p&gt;The package keeps graph data on its own &lt;code&gt;project_graph&lt;/code&gt; database connection, separate from application data. A local SQLite fallback makes the command workflow available without turning the primary database into an architecture index. Build or refresh the graph when you need current answers, then check its state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/semitexa ai:review-graph:generate &lt;span class="nt"&gt;--json&lt;/span&gt;
bin/semitexa ai:review-graph:stats &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;stats&lt;/code&gt; reports the indexed files, nodes, edges, and generation time. The counts change as the repository changes; the useful check is that the graph covers the code you are about to reason about. The &lt;a href="https://framework.semitexa.com/demo/project-graph/overview" rel="noopener noreferrer"&gt;Project Graph overview demo&lt;/a&gt; explains when to reach for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real Project Graph query from Semitexa Demo
&lt;/h2&gt;

&lt;p&gt;Take the original Framework blog index, which now redirects to the canonical articles on semitexa.com. Its &lt;code&gt;BlogIndexPayload&lt;/code&gt; still declares the old route, and &lt;code&gt;BlogIndexHandler&lt;/code&gt; handles that payload. The following query examines that Demo module relationship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/semitexa ai:review-graph:query &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--usages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Semitexa\Demo\Application\Payload\Request\BlogIndexPayload'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--compact&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result names &lt;code&gt;BlogIndexHandler&lt;/code&gt; and labels the relationship &lt;code&gt;handles&lt;/code&gt;. Querying the handler's dependencies also reveals &lt;code&gt;BlogIndexResource&lt;/code&gt; as its produced response and &lt;code&gt;TypedHandlerInterface&lt;/code&gt; as an implemented contract. These are relationships derived from real code, not a diagram that someone must update by hand.&lt;/p&gt;

&lt;p&gt;For a wider view, &lt;code&gt;ai:review-graph:show&lt;/code&gt; renders a module slice, while &lt;code&gt;ai:review-graph:module Demo --include-events --include-flows --format=json&lt;/code&gt; packages a module overview. The &lt;a href="https://framework.semitexa.com/demo/project-graph/inspection" rel="noopener noreferrer"&gt;inspection demo&lt;/a&gt; shows these commands and the questions each one answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check impact before the edit, then give AI the right context
&lt;/h2&gt;

&lt;p&gt;Finding a direct user is only the first step. &lt;code&gt;ai:review-graph:impact&lt;/code&gt; walks downstream relationships and groups affected nodes by distance and module. In the Demo graph used for this example, asking about &lt;code&gt;BlogIndexPayload&lt;/code&gt; identifies &lt;code&gt;BlogIndexHandler&lt;/code&gt; at distance one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/semitexa ai:review-graph:impact &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'Semitexa\Demo\Application\Payload\Request\BlogIndexPayload'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That small result is useful precisely because it is specific. A shared interface or service can produce a much wider radius. Check the result before changing a contract, rather than assuming the file you opened is the whole change surface.&lt;/p&gt;

&lt;p&gt;The same package can prepare focused context for a task with &lt;code&gt;ai:review-graph:context&lt;/code&gt;. For impact work, &lt;code&gt;--context&lt;/code&gt; adds relevant source snippets, and &lt;code&gt;--prompt=review&lt;/code&gt; shapes them for a review. This helps an assistant reason from the affected structure instead of starting with an oversized prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/semitexa ai:review-graph:impact &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'Semitexa\Demo\Application\Payload\Request\BlogIndexPayload'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--context&lt;/span&gt; &lt;span class="nt"&gt;--prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;review
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://framework.semitexa.com/demo/project-graph/impact" rel="noopener noreferrer"&gt;impact and context demo&lt;/a&gt; walks through this workflow and the package's watch mode for long editing sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical Project Graph workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Start with the change you need to make.&lt;/strong&gt; Name the route, class, event, or module involved.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Ask one structural question.&lt;/strong&gt; Use &lt;code&gt;query --usages&lt;/code&gt; or &lt;code&gt;--dependencies&lt;/code&gt; for direct relationships, &lt;code&gt;module&lt;/code&gt; for a broader view, or &lt;code&gt;impact&lt;/code&gt; for downstream effects.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Check freshness.&lt;/strong&gt; Read &lt;code&gt;stats&lt;/code&gt; and refresh with &lt;code&gt;generate&lt;/code&gt; when the stored graph no longer reflects the files relevant to your task. Watch mode can keep it current during a long session.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Verify behavior after the edit.&lt;/strong&gt; The graph describes structure extracted from code. It cannot prove that every runtime path or test still works.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is where Project Graph earns its place in Semitexa: the same architecture map serves onboarding, refactoring, code review, and AI-assisted work. It gives each task a narrow, inspectable starting point while keeping the source code and tests as the final authority.&lt;/p&gt;




&lt;h2&gt;
  
  
  See what your next change can touch.
&lt;/h2&gt;

&lt;p&gt;Start with the Project Graph demos, then run the same commands in a Semitexa project with the package installed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://framework.semitexa.com/demo/project-graph/overview" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Explore Project Graph&lt;/a&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://framework.semitexa.com/demo/project-graph/inspection" rel="noopener noreferrer"&gt;Inspect the graph&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://framework.semitexa.com/demo/get-started/installation" rel="noopener noreferrer"&gt;Get started with Semitexa&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project Graph FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is Semitexa Project Graph required for every application?
&lt;/h3&gt;

&lt;p&gt;No. It is an optional package for repositories where structural queries, impact analysis, and task context pay for themselves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is a project graph the same as a text search?
&lt;/h3&gt;

&lt;p&gt;No. Text search finds matching bytes. A graph query follows typed relationships between code entities, such as which handler consumes a payload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Project Graph replace tests?
&lt;/h3&gt;

&lt;p&gt;No. It estimates structural reach from the graph it has built. Run tests and runtime checks to verify actual behavior after changing code.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does the graph help an AI coding assistant?
&lt;/h3&gt;

&lt;p&gt;It can supply a task-specific view of relevant nodes, edges, and source snippets. That makes a review or implementation prompt more focused and easier to inspect.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was first published on the &lt;a href="https://semitexa.com/blog/project-graph-semitexa" rel="noopener noreferrer"&gt;Semitexa blog&lt;/a&gt;, where the live demos run next to the text. Semitexa is on &lt;a href="https://github.com/semitexa" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>ai</category>
      <category>architecture</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Streaming SSE with Semitexa: Live PHP Updates and HTML</title>
      <dc:creator>Taras Hanych</dc:creator>
      <pubDate>Mon, 28 Sep 2026 10:37:23 +0000</pubDate>
      <link>https://dev.to/semitexa/streaming-sse-with-semitexa-live-php-updates-and-html-3k2f</link>
      <guid>https://dev.to/semitexa/streaming-sse-with-semitexa-live-php-updates-and-html-3k2f</guid>
      <description>&lt;p&gt;&lt;em&gt;A page is ready, but its chart is still loading. A background job advances, but the browser has no reason to ask again. Streaming SSE lets the server send each update as it becomes ready over one open HTTP response.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is streaming SSE?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Streaming SSE&lt;/strong&gt; means keeping an HTTP response open and sending a sequence of Server-Sent Events instead of waiting to return one complete response. The browser reads the &lt;code&gt;text/event-stream&lt;/code&gt; response with &lt;code&gt;EventSource&lt;/code&gt;. Each event ends with a blank line; when that frame arrives, JavaScript can act on it while the connection stays open.&lt;/p&gt;

&lt;p&gt;Consider a dashboard with a fast heading and a slow chart. A conventional request either makes the whole page wait or asks the browser to fetch the chart separately. A streaming response can deliver the initial page now and the chart when it is rendered. Later events can carry fresh status without a timer that repeatedly polls the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event: progress
data: {"job":"report-42","percent":40}

event: progress
data: {"job":"report-42","percent":80}

event: complete
data: {"job":"report-42","url":"/reports/42"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those frames illustrate the &lt;a href="https://html.spec.whatwg.org/multipage/server-sent-events.html" rel="noopener noreferrer"&gt;standard SSE wire format&lt;/a&gt;. SSE is the delivery mechanism, not a job queue or a database of missed messages. Your application still decides what to send, who can receive it, and how to recover after a disconnect. If you need the protocol basics first, read our &lt;a href="https://semitexa.com/blog/server-sent-events-explained" rel="noopener noreferrer"&gt;Server-Sent Events guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How streaming SSE works in Semitexa
&lt;/h2&gt;

&lt;p&gt;Semitexa has two complementary ways to see the idea in action. The &lt;a href="https://framework.semitexa.com/demo/events/sse" rel="noopener noreferrer"&gt;SSE stream demo&lt;/a&gt; opens an &lt;code&gt;EventSource&lt;/code&gt; connection and shows named events generated by the backend. The &lt;a href="https://framework.semitexa.com/demo/rendering/deferred" rel="noopener noreferrer"&gt;deferred blocks demo&lt;/a&gt; starts with server-rendered HTML and streams completed regions into their placeholders. Both use the PHP/Swoole runtime; the browser is observing actual server output.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Render a useful page first.&lt;/strong&gt; The deferred view sends the page shell and skeleton regions without waiting for every slot to finish.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Render slow regions on the server.&lt;/strong&gt; A slot handler supplies data and a Twig template produces the final HTML. The browser does not have to rebuild that region from JSON.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Deliver results over SSE.&lt;/strong&gt; The deferred runtime opens its &lt;code&gt;/__semitexa_kiss&lt;/code&gt; stream, receives completed blocks, and replaces their placeholders. When the page also has live UI events, the runtime can use the same session and channel instead of opening a separate stream for each feature.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the useful Semitexa difference for a server-rendered product: streaming is part of the page delivery model. You can keep your view logic in PHP and Twig, while the transport moves finished HTML to the page as soon as each region is ready. The &lt;a href="https://framework.semitexa.com/demo/rendering" rel="noopener noreferrer"&gt;rendering demos&lt;/a&gt; show this alongside other SSR features.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Two distinct use cases:&lt;/strong&gt; use named SSE events when the browser needs to interpret data such as job progress. Use deferred HTML delivery when the server already owns the UI region. Semitexa supports both; the right choice follows what the client needs to do with an update.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A real Semitexa deferred slot
&lt;/h2&gt;

&lt;p&gt;The chart in Semitexa Demo is declared as a typed slot resource. Here is the relevant part of its actual declaration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;AsSlotResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'demo_deferred_blocks'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'deferred_chart_widget'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'@project-layouts-semitexa-demo/deferred/chart-widget.html.twig'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deferred&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;skeletonTemplate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'@project-layouts-semitexa-demo/deferred/chart-widget.skeleton.html.twig'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;clientModules&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'@project-static-semitexa-demo/deferred/chart-widget.js'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DeferredChartWidgetSlot&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HtmlSlotResponse&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The slot handler supplies chart data to this resource.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The declaration connects a page handle and slot name to its final Twig template and a skeleton. The page template must place the deferred slot, and its handler must populate the resource. Once wired, the runtime handles late delivery over SSE. &lt;code&gt;clientModules&lt;/code&gt; adds behavior for this chart after insertion; it is not required just to receive the HTML.&lt;/p&gt;

&lt;p&gt;This matters when a page has several independent slow regions. A product list can be ready while analytics is still computing. Users see the content that is ready, and each later block arrives in the same page instead of forcing an all-or-nothing render. The &lt;a href="https://framework.semitexa.com/demo/rendering/deferred" rel="noopener noreferrer"&gt;deferred blocks example&lt;/a&gt; lets you inspect the resulting page and shared stream.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try streaming SSE in the live Semitexa demos
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Watch a backend event stream
&lt;/h3&gt;

&lt;p&gt;Open the &lt;a href="https://framework.semitexa.com/demo/events/sse" rel="noopener noreferrer"&gt;SSE stream demo&lt;/a&gt;, sign in, and connect. The client displays a connection event and listens for named messages such as &lt;code&gt;notification&lt;/code&gt; and &lt;code&gt;scheduler.tick&lt;/code&gt;. In a debug-enabled demo environment, its countdown follows the next minute boundary and the tick comes from a server-side producer, not the countdown timer. The showcase producer is deliberately disabled when &lt;code&gt;APP_DEBUG&lt;/code&gt; is off; ordinary application streams do not depend on it. The page exposes the handler and client JavaScript beside the preview.&lt;/p&gt;

&lt;h3&gt;
  
  
  Watch server-rendered HTML arrive
&lt;/h3&gt;

&lt;p&gt;Open the &lt;a href="https://framework.semitexa.com/demo/rendering/deferred" rel="noopener noreferrer"&gt;deferred blocks demo&lt;/a&gt; to see the page shell and skeletons before completed regions arrive. Its stream observer reports the lifecycle of the shared &lt;code&gt;/__semitexa_kiss&lt;/code&gt; connection. The runtime owns that connection; the observer does not open another one. A sign-in is required for the persistent stream shown in this demo.&lt;/p&gt;

&lt;p&gt;The distinction is visible in the browser: one demo shows event data, and the other shows finished HTML replacing a placeholder. Together they show why SSE streaming is useful beyond a console that prints messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to plan before using streaming SSE in production
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep frames flowing.&lt;/strong&gt; Proxy buffering and compression can delay small events. Check the complete path from Swoole to the browser. NGINX documents &lt;a href="https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering" rel="noopener noreferrer"&gt;proxy buffering&lt;/a&gt; and the &lt;code&gt;X-Accel-Buffering&lt;/code&gt; response header.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control connection cost.&lt;/strong&gt; A stream stays open, so budget for concurrent viewers, idle timeouts, heartbeats, and disconnect cleanup. The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events" rel="noopener noreferrer"&gt;MDN SSE guide&lt;/a&gt; also explains browser connection limits, especially over HTTP/1.x.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorize the data.&lt;/strong&gt; A live stream can outlast the request that opened the page. Check who may subscribe and what each event may reveal. Semitexa's persistent demo stream asks for authentication before it opens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design recovery.&lt;/strong&gt; &lt;code&gt;EventSource&lt;/code&gt; normally reconnects after interruption. Reconnection alone does not replay missed updates. Decide whether a view can refresh its current state or needs event IDs, retention, and replay.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Measure the real delivery path. A frame that leaves PHP immediately but sits in a proxy buffer is not a live update for your user.&lt;/p&gt;




&lt;h2&gt;
  
  
  Build a live PHP page with Semitexa.
&lt;/h2&gt;

&lt;p&gt;Explore the two working streaming flows and use the installation guide to try Semitexa in your own project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://framework.semitexa.com/demo/rendering/deferred" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Explore deferred HTML&lt;/a&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://framework.semitexa.com/demo/events/sse" rel="noopener noreferrer"&gt;Watch the SSE stream&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://framework.semitexa.com/demo/get-started/installation" rel="noopener noreferrer"&gt;Get started&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Streaming SSE FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is streaming SSE different from Server-Sent Events?
&lt;/h3&gt;

&lt;p&gt;No. It emphasizes the way an SSE response stays open and delivers multiple events over time. Each completed event can update the browser before the response ends.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Semitexa require a single-page application for streaming?
&lt;/h3&gt;

&lt;p&gt;No. Deferred slots start from a server-rendered page and deliver completed Twig HTML into it. Client JavaScript handles transport and insertion, while the server continues to own the region's markup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can the browser send messages back through the SSE connection?
&lt;/h3&gt;

&lt;p&gt;No. SSE sends data from server to browser. Use a normal HTTP request to start or change work, then use the stream to receive progress or refreshed content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will EventSource recover every missed update?
&lt;/h3&gt;

&lt;p&gt;No. It reconnects, but durable delivery needs application-level state, replay, or a fresh snapshot after reconnecting.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was first published on the &lt;a href="https://semitexa.com/blog/streaming-sse-semitexa" rel="noopener noreferrer"&gt;Semitexa blog&lt;/a&gt;, where the live demos run next to the text. Semitexa is on &lt;a href="https://github.com/semitexa" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>webdev</category>
      <category>realtime</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Server-Sent Events Explained: How SSE Works and When to Use It</title>
      <dc:creator>Taras Hanych</dc:creator>
      <pubDate>Mon, 28 Sep 2026 10:37:17 +0000</pubDate>
      <link>https://dev.to/semitexa/server-sent-events-explained-how-sse-works-and-when-to-use-it-87j</link>
      <guid>https://dev.to/semitexa/server-sent-events-explained-how-sse-works-and-when-to-use-it-87j</guid>
      <description>&lt;p&gt;&lt;em&gt;A report finishes. An import moves forward. A dashboard gets fresh data. Your server already knows something changed. How does the browser find out?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One answer is to ask again every few seconds. Another is to keep a connection open and let the server send an update when it has something to say. That second approach is where &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt; fit: a small HTTP protocol with useful consequences for application design.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are Server-Sent Events?
&lt;/h2&gt;

&lt;p&gt;SSE lets a server send a sequence of text messages to a browser over a long-lived HTTP response. The browser opens the connection with &lt;code&gt;EventSource&lt;/code&gt;; the server responds with &lt;code&gt;Content-Type: text/event-stream&lt;/code&gt;. Each completed event becomes available to JavaScript without waiting for the response to finish.&lt;/p&gt;

&lt;p&gt;The stream carries updates in one direction: server to browser. A user can still submit a form or make a separate HTTP request to start a job. SSE carries the progress back. This separation is often a natural fit for a product: commands go in, status updates come out.&lt;/p&gt;

&lt;p&gt;Think of an export screen. The user clicks “Generate report,” the application queues the work, and the page starts showing progress. The user should not need to refresh, and the backend should not need to pretend every report takes the same amount of time.&lt;/p&gt;

&lt;h2&gt;
  
  
  How SSE works, step by step
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser      GET /events →       Server
Listening    ← event: progress   Job running
Updated UI   ← event: complete   Job finished
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;One HTTP response carries multiple events over time. The browser updates the relevant part of the page after each message.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A message is UTF-8 text with fields on separate lines. A blank line terminates the event. Here is an illustrative response body, with two events on the same stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;retry: 3000
id: 41
event: progress
data: {"jobId":"export-7","percent":50}

id: 42
event: complete
data: {"jobId":"export-7","percent":100}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;data&lt;/code&gt; carries the message. JSON is a useful convention; SSE itself does not require it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;event&lt;/code&gt; names the event. Without it, the browser dispatches a &lt;code&gt;message&lt;/code&gt; event.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt; sets the last event ID, which the browser can send as &lt;code&gt;Last-Event-ID&lt;/code&gt; when reconnecting.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retry&lt;/code&gt; sets a reconnection delay in milliseconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These fields and parsing rules are defined in the &lt;a href="https://html.spec.whatwg.org/multipage/server-sent-events.html" rel="noopener noreferrer"&gt;WHATWG HTML standard for Server-Sent Events&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Reconnecting is not the same as recovering.&lt;/strong&gt; An event ID does not store messages for you. If a missed update matters, your backend needs retained events and a replay policy, or a way for the client to fetch the current state after reconnecting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A small JavaScript EventSource example
&lt;/h2&gt;

&lt;p&gt;This browser example assumes your application serves the event stream above from &lt;code&gt;/events&lt;/code&gt;. It is a protocol illustration, not a built-in Semitexa endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"job-status"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Connecting…&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#job-status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;progress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Export: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;percent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;complete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your export is ready.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLOSED&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connection closed. Reload to try again.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connection interrupted. Reconnecting…&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;addEventListener()&lt;/code&gt; for named events, and &lt;code&gt;onmessage&lt;/code&gt; for unnamed messages. The native client normally retries interrupted connections; calling &lt;code&gt;close()&lt;/code&gt; explicitly stops it. A production client should also validate payloads and make repeated updates safe. The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/EventSource" rel="noopener noreferrer"&gt;MDN EventSource reference&lt;/a&gt; describes the client API and its connection states.&lt;/p&gt;

&lt;p&gt;On the PHP side, encoding an event is straightforward: a name, a JSON payload, and the terminating blank line. Keeping many connections alive is the architectural decision. Avoid copying an infinite blocking loop into a normal page handler. Choose a runtime and streaming integration that can handle concurrent connections, disconnects, and cancellation.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSE vs. WebSockets vs. polling
&lt;/h2&gt;

&lt;p&gt;Start with the traffic your product needs. A progress indicator and a multiplayer game have very different conversations with their servers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;SSE&lt;/th&gt;
&lt;th&gt;WebSocket&lt;/th&gt;
&lt;th&gt;Polling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;How do updates arrive?&lt;/td&gt;
&lt;td&gt;Server sends events over an HTTP response.&lt;/td&gt;
&lt;td&gt;Both peers send messages over an open connection.&lt;/td&gt;
&lt;td&gt;Client makes repeated HTTP requests.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical payload&lt;/td&gt;
&lt;td&gt;Text, often JSON or HTML.&lt;/td&gt;
&lt;td&gt;Text or binary messages.&lt;/td&gt;
&lt;td&gt;Whatever the HTTP endpoint returns.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Good starting point&lt;/td&gt;
&lt;td&gt;Job progress, notifications, live dashboards.&lt;/td&gt;
&lt;td&gt;Frequent two-way interaction or binary traffic.&lt;/td&gt;
&lt;td&gt;Infrequent updates where a delay is acceptable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reconnect behavior&lt;/td&gt;
&lt;td&gt;Native EventSource includes reconnection.&lt;/td&gt;
&lt;td&gt;Application or client library manages recovery.&lt;/td&gt;
&lt;td&gt;The next request is another opportunity to refresh.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" rel="noopener noreferrer"&gt;WebSocket API&lt;/a&gt; supports two-way messaging. Choose it when that capability earns its operational cost. Choose polling when simplicity and a relaxed freshness requirement make it sufficient. Choose SSE when most updates originate on the server and should reach an open page promptly.&lt;/p&gt;

&lt;p&gt;A useful design exercise: write down every message your screen sends. If the browser mostly says “start this task” once, then listens for twenty progress updates, separate HTTP commands plus an SSE stream are worth evaluating. Measure under your own workload before making latency or capacity claims.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to check before running SSE in production
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Make sure each event reaches the browser promptly
&lt;/h3&gt;

&lt;p&gt;A correct stream can still feel broken if a proxy buffers it. Review buffering, compression, and idle timeouts across the complete path. With NGINX, &lt;code&gt;proxy_buffering off&lt;/code&gt; or an appropriate &lt;code&gt;X-Accel-Buffering: no&lt;/code&gt; response header can control proxy buffering, depending on configuration. See the &lt;a href="https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering" rel="noopener noreferrer"&gt;NGINX proxy buffering documentation&lt;/a&gt;. Test through the actual reverse proxy, not only directly against PHP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plan for quiet periods and multiple tabs
&lt;/h3&gt;

&lt;p&gt;Comment lines such as &lt;code&gt;: heartbeat&lt;/code&gt;, followed by a blank line, can keep an otherwise idle stream active. Send them more frequently than the shortest relevant idle timeout. HTTP/1.x browsers impose a small per-origin connection budget; HTTP/2 multiplexing helps, but still has negotiated stream limits. Prefer sharing a stream across features instead of opening one for every widget. See &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events" rel="noopener noreferrer"&gt;MDN’s SSE guide&lt;/a&gt; for heartbeat and connection-limit details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treat subscriptions as access to data
&lt;/h3&gt;

&lt;p&gt;Authorize the connection and the events it receives. Native &lt;code&gt;EventSource&lt;/code&gt; does not expose an option for arbitrary request headers. Same-origin cookie authentication is one practical choice; cross-origin credentials require deliberate CORS configuration. Avoid putting long-lived secrets in URLs. Decide what happens when a session expires or access to a resource changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Budget for slow clients and recovery
&lt;/h3&gt;

&lt;p&gt;Put limits on pending output and connection lifetimes. Coalesce updates when only the latest value matters. For an import progress bar, receiving the current 80% state may be enough. For a business activity feed, missing items may be unacceptable. That difference should drive your retention, event IDs, replay behavior, and monitoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where SSE fits in Semitexa Framework
&lt;/h2&gt;

&lt;p&gt;Semitexa is a PHP framework built around typed payloads, handlers, and resources, with a Swoole runtime and server-rendered Twig views. Its SSR package includes deferred regions and live transport: a page can arrive with useful HTML, then receive server-produced updates for the parts that change.&lt;/p&gt;

&lt;p&gt;This is useful when you want the server to remain responsible for presentation. An order status, report panel, or operational dashboard can use server-rendered regions rather than requiring a second implementation of the same rendering rules in browser code. SSE is the transport; your handlers and resources still determine what the user is allowed to see.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://framework.semitexa.com/demo/events/sse" rel="noopener noreferrer"&gt;Semitexa SSE demo&lt;/a&gt; shows backend-generated messages over a live connection and exposes the handler and client source. Opening its stream requires sign-in. For the broader rendering model, explore the &lt;a href="https://framework.semitexa.com/demo/rendering" rel="noopener noreferrer"&gt;SSR and live interface examples&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Start with one screen that has a clear need for server updates. Define the initial HTML, decide which region changes, and describe what should happen after a disconnect. Then use the live demo and source to evaluate the framework against those requirements.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try the flow. Read the code. Make it yours.
&lt;/h2&gt;

&lt;p&gt;See SSE running in Semitexa, then follow the installation guide to explore it in your own application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://framework.semitexa.com/demo/get-started/installation" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Get started with Semitexa&lt;/a&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://framework.semitexa.com/demo/events/sse" rel="noopener noreferrer"&gt;Explore the live SSE demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common questions about SSE
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can SSE replace WebSockets?
&lt;/h3&gt;

&lt;p&gt;For server-to-browser updates, it may be enough. For frequent bidirectional messages or binary payloads, WebSockets may be a better fit. The decision follows your application's traffic, not a universal ranking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does SSE guarantee delivery?
&lt;/h3&gt;

&lt;p&gt;No. Reconnection is a transport feature. Recovery requires application logic: retained events, replay, deduplication, or refreshing the current state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use SSE with PHP?
&lt;/h3&gt;

&lt;p&gt;Yes. PHP can produce event-stream responses. The important questions are how your runtime handles long-lived connections and whether the complete delivery path flushes updates promptly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a single-page application?
&lt;/h3&gt;

&lt;p&gt;No. SSE can enhance a server-rendered page. Start with HTML, then update the region that needs fresh information. That is the approach the Semitexa rendering examples help you explore.&lt;/p&gt;

&lt;p&gt;Ready to see it in a working PHP application? Read &lt;a href="https://semitexa.com/blog/streaming-sse-semitexa" rel="noopener noreferrer"&gt;Streaming SSE with Semitexa&lt;/a&gt; for the live event and deferred HTML demos.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was first published on the &lt;a href="https://semitexa.com/blog/server-sent-events-explained" rel="noopener noreferrer"&gt;Semitexa blog&lt;/a&gt;, where the live demos run next to the text. Semitexa is on &lt;a href="https://github.com/semitexa" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>realtime</category>
      <category>php</category>
    </item>
  </channel>
</rss>
