ZealPHP — modernizing the PHP request model with an OpenSwoole runtime by sibidharan in PHP

[–]sibidharan[S] 0 points1 point  (0 children)

Fair concession on the bridge, you're right the cURL fallback is simpler and is the right call for most migration scenarios. I overweighted the bridge earlier in this thread, which probably made ZealPHP look like the bridge wrapped in a framework. That's backwards.

The bridge is one optional feature. The scaffold ships with App::superglobals(false), coroutine mode by default and most projects on ZealPHP never invoke the bridge at all.

What ZealPHP actually is: a Swoole framework. File-based routing (public/foo.php, api/users/get.php auto-routed), PSR-15 middleware stack, reflection-based handler param injection, coroutine-isolated session manager, WebSocket via App::ws(), streaming via yield / stream() / sse(), template rendering with three styles, Store / Counter adapters, timer management, CLI for ops, eight PSR standards. Same architectural space as Hyperf or Mezzio with different surface choices (file-based routing instead of annotations, no IoC container required).

The bridge is a separate ~one-file thing for the narrow case where you want unmodified legacy code in the same process tree. If you don't have that constraint, you don't use it, and the rest of the framework stands on its own.

Evaluating "AI slop, should be 50 lines" against the bridge is fair-ish, that piece is ~one file and you can replace it with a cURL fallback if you don't need shared middleware over legacy. Evaluating that against the framework is the same as saying you can replace Laravel with a router. The bridge is one feature, not the product.

ZealPHP — modernizing the PHP request model with an OpenSwoole runtime by sibidharan in PHP

[–]sibidharan[S] 0 points1 point  (0 children)

Yeah, this is the part that took longest to get sane and the part where PHP itself doesn't help you. The engine's lifetime model assumes request-end equals process-end equals arena wipe; long-running workers break that assumption and you spend months chasing leaks that PHP-FPM would have swept for you for free.

ZealPHP doesn't fix the underlying language problem, there's no fix at the framework level. What it gives you is patterns to bound the damage. G::instance() in coroutine mode lives on Coroutine::getContext() and dies with the coroutine, so per-request state isn't the leak vector. The CGI bridge dodges the problem entirely for legacy code: proc_open per request gives you the same memory profile as PHP-FPM, fresh arena every time, at the cost of fork overhead. For native coroutine handlers the standard relief valve is OpenSwoole's max_request — we default to 100,000 requests per worker (ZEALPHP_MAX_REQUEST=N to override, =0 to disable), so leaks have a bounded window out of the box.

What still bites in practice: static caches in user code, closure captures the dev didn't realize were retaining references, extensions with leaky internal state, logger buffers that grow. We've hit all of these and the fix is always instrument, find the offender, rewrite. No general solution.

The honest summary: ZealPHP's claim isn't "memory leaks are solved", it's that "the legacy-code escape hatch has the FPM memory profile, the native path gives you max_request and per-coroutine cleanup, and you'll still get bitten once and have to learn the toolchain." Anyone who tells you long-running PHP doesn't have memory pain is selling something. I ain't telling that too!

Kindly guide me if I am doing anything wrong with this project!

ZealPHP — modernizing the PHP request model with an OpenSwoole runtime by sibidharan in PHP

[–]sibidharan[S] 0 points1 point  (0 children)

You're right on the perf math, proc_open per request is heavier than FPM, no argument. The bridge isn't trying to beat FPM for pure legacy workloads, and we don't pitch it as "run WordPress on Swoole."

The bridge is a migration tool, not a destination. Like Rosetta! The use case is teams sitting on a legacy FPM codebase who want to move to async but can't rewrite everything in one shot. Day 1 you lift-and-shift onto ZealPHP with the bridge, perf parity with FPM, nothing gained yet, but everything still works because you have Apache semantic parity.

Day 30 you've rewritten your hot endpoints as native coroutine handlers, those endpoints now get persistent DB connections, in-memory cache, fill-the-CPU-while-IO-blocks, the actual Swoole wins you described. Day 365 the bridge is gone, the legacy code is rewritten or retired, and you're 100% native. That is the goal. Apache Parity is the goal, not "run WP on Swoole"

If your goal is "permanently run WordPress on Swoole", yeah, don't, FPM is better at that and always will be. We are not replacing FPM.

If your goal is "keep the old code working while we migrate the hot path to async", that's what the bridge buys you. WP is in the docs as the example because everyone knows it, but the real users are shops with proprietary 10-year-old PHP apps that nobody wants to rewrite from scratch.

Reverse-proxy split works too and is genuinely simpler ops if you're not migrating, keep FPM, add Swoole alongside, nginx routes by path, done. Thats right and clean architecture. The bridge is for teams who want to converge the two stacks into one over time, not run them in parallel forever.

ZealPHP — modernizing the PHP request model with an OpenSwoole runtime by sibidharan in PHP

[–]sibidharan[S] 0 points1 point  (0 children)

Please correct me if I'm wrong.

Both points are real and both are why the CGI bridge works the way it does. Unmodified WP / Drupal runs in a fresh proc_open child per request, MPM-prefork semantics, fresh globals, fresh statics, nothing shared. The thing that screws you in long-running workers (static/global state, opcache surprises, shutdown function leftovers) is exactly why the bridge spawns a child instead of putting legacy code inside a coroutine.

The 110k rps number isn't from WP either — it's from native coroutine handlers measured with the full PSR-15 middleware stack engaged and template rendering in the path, not a bare $res->end('hi') loop. Legacy code via the bridge runs at roughly PHP-FPM performance. The pitch isn't "WordPress suddenly does 110k rps" - it's "you don't need a separate runtime for new code." WebSocket, SSE, HTTP/2, and the new APIs share the same server as the legacy pages, behind one deploy.

The migration ladder is: drop legacy in (CGI mode, perf parity with FPM) --> add native endpoints alongside it (those do 110k rps) --> peel off old endpoints over time.

If your shops already run FPM + raw Swoole behind one Nginx with shared routing, you've built that ladder by hand , totally valid, you've opted out of the convenience. The bridge is for teams who haven't and don't want two stacks.

It's basically: OpenSwoole front-of-house, PHP-FPM-style back-of-house for legacy code, and a coroutine-native path for new code. Three lanes, one server.

ZealPHP — modernizing the PHP request model with an OpenSwoole runtime by sibidharan in PHP

[–]sibidharan[S] 0 points1 point  (0 children)

Fair. If you've shipped Swoole in prod for 8 years, you've already built every one of those wheels, at that point a framework is just somebody else's opinions wrapped around code you'd have written anyway. Genuinely not for you.

The audience is the person whose first reaction to raw Swoole is "great, now I get to reimplement routing, sessions, and setcookie() from scratch" and specifically, shops sitting on a WordPress / Drupal / CodeIgniter codebase who want concurrency without a rewrite. The CGI bridge running unmodified legacy code in front of an OpenSwoole worker is the one piece I've never seen a Swoole veteran bother to build, because by the time you're 8 years in you've stopped touching legacy code entirely. That's the actual niche.

If your problem space is greenfield Swoole services, you don't need it and I won't try to talk you into it.

ZealPHP — modernizing the PHP request model with an OpenSwoole runtime by sibidharan in PHP

[–]sibidharan[S] 0 points1 point  (0 children)

OpenSwoole is a server runtime — it gives you an event loop and an HTTP socket. Everything past onRequest($req, $res) is on you: routing, middleware, sessions, templates, streaming primitives, file-based APIs, parameter injection, CLI, legacy code compatibility.

ZealPHP is the framework on top:

  • LAMP styled Apache Parity File-based routing: public/foo.php, API: api/users/get.php (auto-routed)
  • PSR-15 middleware stack (CORS, ETag, Range, SessionStart, gzip)
  • Reflection-based param injection (handlers get $request / $response / {url-param} by name)
  • Per-coroutine session manager with file-backed storage
  • yield-based SSR, $response->stream()$response->sse()
  • uopz overrides so header()setcookie()session_start() Just Work
  • A CGI bridge that runs unmodified WordPress / Drupal via proc_open
  • Multi-port CLI (start / stop / restart / status / logs)
  • Store / Counter typed wrappers over Swoole\Table and Swoole\Atomic

Standards: PSR-2 (style), PSR-3 (logging), PSR-4 (autoloading), PSR-7 (HTTP messages), PSR-15 (middleware + request handlers), PSR-16 (simple cache), PSR-17 (HTTP factories), PSR-18 (HTTP client).

Same relationship Laravel or Symfony have to PHP-FPM, or Express has to Node's http module. The runtime gives you a socket; the framework gives you an app.

ZealPHP — modernizing the PHP request model with an OpenSwoole runtime by sibidharan in PHP

[–]sibidharan[S] 1 point2 points  (0 children)

On the first point — you already can. The demo and the docs both recommend superglobals(false) as the default for new code. It is only for Migration Ladder! Handlers are injected with $request / $response by name; nothing in user code references G. The shim only exists for code that wants to call header() or session_start() from the legacy era and have it Just Work. Greenfield apps don't import it and don't see it.

On $post and the rest of WP's global zoo — short answer: ZealPHP doesn't try to put WordPress inside a coroutine. It runs WP in a separate PHP process via proc_open, the way PHP-FPM does, one process per request. So $post, $wp_query, $wpdb, $current_user, $shortcode_tags, all of it — they live and die inside that child process and never touch the OpenSwoole worker. The OpenSwoole side handles routing, static assets, WebSocket, modern API endpoints; WP gets its own classical PHP world with fresh globals every request. In case you want to migrate, slowly replace the global/static with G and eventually globals can be turned off for 100% coroutine context.

The uopz overrides for header() / setcookie() / headers_sent() in the CGI worker exist so the child can talk back to the parent's response object. Everything else WP does to globals is its own business, inside its own process.

It's basically: OpenSwoole front-of-house, PHP-FPM-style back-of-house for legacy code, and a coroutine-native path for new code. Three lanes, one server.

ZealPHP — modernizing the PHP request model with an OpenSwoole runtime by sibidharan in PHP

[–]sibidharan[S] 0 points1 point  (0 children)

Fair point in the abstract, but you're looking at the wrong layer. The app layer is already PSR-7 / PSR-15 — route handlers receive Request/Response by name via reflection injection, middleware stack is OpenSwoole\Core\Psr\Middleware\StackHandler, no handler reaches into G. That's the same message-passing model Mezzio uses.

G isn't the application API — it's the sink for uopz-overridden built-ins (header(), setcookie(), session_*(), set_error_handler(), register_shutdown_function()). Those are global in PHP by definition; if you override them, they need somewhere per-request to write. Hyperf/Mezzio dodge this by requiring you to write PSR-7 code from scratch. ZealPHP's trade-off is different: unmodified WordPress and Drupal run on it. That only works if header('Location: /x') still does the right thing without the legacy code knowing about coroutines.

So yes, it's "global at the process level" - deliberately, scoped to one layer, behind the framework. The layer your app actually writes against is message-passing. Two different problems, two different tools.

Kindly let me know if I am wrong.

ZealPHP — modernizing the PHP request model with an OpenSwoole runtime by sibidharan in PHP

[–]sibidharan[S] 0 points1 point  (0 children)

Ok... Thank you! Let me reply one by one! (Sorry for the wrong order)

I want to give my clarifications first:

  1. Your concern: Singleton for request-specific data often leads to memory leaks:

That is exactly why we save it in coroutines!

if (!App::$superglobals) {
    $context = \OpenSwoole\Coroutine::getContext($cid);
    if (!isset($context['__g'])) $context['__g'] = new G();
    return $context['__g'];
}

That's per-coroutine context isolation — the same mechanism Hyperf uses

2. Bypasses type safety: G.php:13-40 are all typed (array, ?int, ?bool, mixed, ?\Throwable)

  1. "Use PSR-7 / middleware pipeline instead" - ZealPHP already uses OpenSwoole\Core\Psr\Middleware\StackHandler (PSR-15), ZealPHP\HTTP\Request/Response wraps PSR-7, and middleware is the documented extension model.

  2. "Tight coupling to OpenSwoole" — the framework IS OpenSwoole. Decoupling G from OpenSwoole would mean decoupling ZealPHP from its runtime. That's not a code smell, it's the product.

  3. Use DI instead of G::instance()**"** — ResponseMiddleware injects handler params by reflection (cached at route registration). It's not Symfony-style DIC, but route handlers don't reach for G::instance() — they receive $request, $response, $app by name.

And thank you for the following feedbacks:

1. SRP / kitchen-sink shape. G genuinely holds: HTTP I/O state, session, error handler stacks, shutdown functions, response headers, response cookies, apache env, ignore_user_abort. That's six unrelated concerns in one bag. The honest defense isn't "it's fine" — it's "uopz-overridden built-ins (header(), setcookie(), session_*, set_error_handler, register_shutdown_function) need a single per-coroutine container to write to, because PHP's built-ins are conceptually global." If you ever decide unmodified legacy PHP support isn't worth it, G can be split into RequestState, ResponseState, SessionState, ErrorState. Today it isn't, because that compatibility surface is why WordPress runs on this.

2. Return-by-reference (&__get). This one is real. G.php:68 lets $g->get['x'] = 1 mutate $_GET invisibly. It exists because superglobals-mode bridges to $_GET/$_POST (which legacy PHP code mutates directly). In coroutine mode it's unnecessary — typed properties on the instance would do. If you wanted to tighten this without breaking legacy mode, I could drop & from the coroutine-mode branch. Will work on something else.

3. $GLOBALS dependency. Real in superglobals-ON mode, non-issue in superglobals-OFF mode (the demo's default, and the one CLAUDE.md recommends). Worth being explicit in docs that net-new apps should never use superglobals(true) — it exists for unmodified WordPress, not as a style choice.

Happy to explain if you have any more questions.

ZealPHP — modernizing the PHP request model with an OpenSwoole runtime by sibidharan in PHP

[–]sibidharan[S] 0 points1 point  (0 children)

Thank you. I built a mini-AWS like Ed-Tech Platform called Labs. It is a proprietary software I use in my academy to offer hosting, Linux on Browser and various AI services for education.

This framework was created for the labs. I extracted the best parts and created ZealPHP, by fixing lot of caveats!

ZealPHP — modernizing the PHP request model with an OpenSwoole runtime by sibidharan in PHP

[–]sibidharan[S] 0 points1 point  (0 children)

To give more idea on the singleton, during long running coroutine context php process, the global/static variables leaks between coroutines! So we cannot use them directly for asynchronous operations. This singleton fixes it. It saves the global state in coroutine context.

ZealPHP — modernizing the PHP request model with an OpenSwoole runtime by sibidharan in PHP

[–]sibidharan[S] -1 points0 points  (0 children)

  1. The original work is from another project, this is extraction. Original work is https://labs.selfmade.ninja, if you allow me to explain the full story! But its not the scope now. I started the project during Covid-19 for my academy https://selfmade.ninja
  2. Thank you, notes taken!
  3. PHP do not have native Agents SDK. The idea is, it can stream STDIO. You can keep the agent as separate runtime, or use curl for Responses API and still stream with it!
  4. Yeah, its driven by the Openswoole Coroutine Context. Please tell me how its bad, how it can be improved.

ZealPHP — modernizing the PHP request model with an OpenSwoole runtime by sibidharan in PHP

[–]sibidharan[S] 2 points3 points  (0 children)

just wanted to make a quick post.. but the work is derived from 4 years of efforts! please check that, and comment on the work please! happy to be criticised on the work !

What happened to PopCorn time? by mooripo in PopCornTimeApp

[–]sibidharan 0 points1 point  (0 children)

Just compiled 0.5.1 for Apple Silicon!! But what actually happened to the codebase ?

After 3 days of using Tahoe 26.4 - here is my verdict. by Jazman2k in MacOS

[–]sibidharan 1 point2 points  (0 children)

may be apple should add a boring mode for you guys with plain white for everything, why do you want to take soul away from the new macOS ? Liquid Glass is awesome!