Skip to content

Logic stream wrapper

Greg Bowler edited this page Apr 14, 2026 · 1 revision

LogicStreamWrapper exists for one specific problem: path-mapped logic files often declare the same function names, such as go().

If we load several of those files in one PHP process, the function names would normally collide. The stream wrapper prevents that by injecting a namespace when the file does not already declare one.

Why this is needed

A typical WebEngine request may load several logic files:

  • page/_common.php
  • page/shop/_common.php
  • page/shop/@category/index.php

All of them may define a function called go(). Without namespacing, PHP would reject the second declaration.

What the wrapper does

When a file is required through the stream wrapper:

  1. the file must begin with <?php
  2. if it already declares a namespace, that namespace is left in place
  3. otherwise a namespace is injected based on the file path
  4. unqualified internal PHP class names are prefixed so built-in types such as DateTime still resolve correctly inside the injected namespace

The generated namespace prefix is:

GT\AppLogic

with the file path appended and normalised.

Registering the wrapper

use GT\Routing\LogicStream\LogicStreamWrapper;

stream_wrapper_register("gt-logic-stream", LogicStreamWrapper::class);
require "gt-logic-stream://page/shop/_common.php";

Namespace generation

The namespace path is handled by LogicStreamNamespace, which converts path separators and unsupported characters into namespace-safe names.

For example, a path such as:

/tmp/page/shop/@category/index.php

becomes a generated namespace rooted under GT\AppLogic.

File requirements

For the wrapper to work correctly:

  • the file must be a PHP file
  • it must begin with an opening PHP tag
  • it should contain only PHP code, and have no side effects (including it should only declare symbols, not echo content)

If the opening tag is missing, the wrapper throws an exception explaining which file failed.

When we need this page

If we are only using class-based router callbacks, we may never need the stream wrapper.

If we are building a filesystem-mapped application runtime, or trying to understand how WebEngine can load many go() functions in one request, this is the missing piece.


To see how the callback router, path matcher, dynamic paths and stream wrapper are used together, read example project.

Clone this wiki locally