upscale/ext-openswoole
Connects Laravel Upscale to the OpenSwoole PHP extension, enabling coroutine-based HTTP serving, task workers, and runtime hooks for high-concurrency Laravel applications. A focused adapter for teams running OpenSwoole-powered Laravel services and workers.
This package is a compatibility shim: it makes code written against ext-swoole's Swoole\* classes run on ext-openswoole (and vice-versa where possible), so you can use one extension in production and keep dependencies/app code untouched.
php -m | grep -i swoole
php -r 'echo SWOOLE_VERSION, PHP_EOL;'
composer require upscale/ext-openswoole
composer.json for an autoload.files bootstrap — that file must be what registers the aliases, and it must be a hard require, not require-dev:
php -r 'var_dump(class_exists("Swoole\\Coroutine"), class_exists("OpenSwoole\\Coroutine"));'
ext-openswoole, but your app code or a third-party package references Swoole\Http\Server, Swoole\Coroutine::getCid(), Swoole\Timer, etc. Add the package, run as usual — the Swoole\* names now resolve to the OpenSwoole implementation.Look first at the package's src//bootstrap file (the alias map is the whole product) and its README. If the map is short, it's worth reading end-to-end before trusting it in production.
Write new code against the Swoole\* namespace, not OpenSwoole\*. That's the namespace the wrapper aliases, so your code stays portable between the two extensions.
Octane integration. Start workers with the OpenSwoole driver and let the shim cover the gaps:
php artisan octane:install --server=openswoole
php artisan octane:start --server=openswoole
Custom middleware or a custom ServerFactory that calls Swoole\Coroutine::getCid() or Swoole\Timer::after() keeps working without edits.
Wrap the runtime behind one small service rather than sprinkling extension calls through the app — this gives you a single place to mock in tests and to swap extensions:
final class CoroutineRunner
{
public function run(callable $fn): mixed
{
return \Swoole\Coroutine\run($fn);
}
public function id(): int
{
return \Swoole\Coroutine::getCid();
}
}
Bind it in a service provider; assert on it in feature tests instead of touching the extension.
CI matrix. Test the same code against both extensions — this is where the wrapper pays off:
strategy:
matrix:
ext: [swoole, openswoole]
steps:
- run: |
if [ "${{ matrix.ext }}" = "openswoole" ]; then pecl install openswoole; else pecl install swoole; fi
- run: composer install && php artisan test
Packages you publish. Put upscale/ext-openswoole in require (not suggest) and code against Swoole\*; consumers on either extension get a working install. If you can't take the dependency, at minimum suggest it and document the assumption.
Feature detection only. Use extension_loaded('openswoole') / extension_loaded('swoole') to branch on capabilities, never to choose a class name.
swoole_* functions, and behavioral differences (Runtime::enableCoroutine hook flags, signal handling, coroutine scheduling nuances) are not normalized. Test the specific features you rely on.get_class() returns the canonical OpenSwoole name after aliasing. Don't branch on it — use instanceof \Swoole\Coroutine.Swoole\* reference — Composer files autoload handles this; a class_alias() in a service provider's register() is too late for pre-boot code (custom Octane server factory, preload scripts).print_r(array_values(array_filter(
get_declared_classes(),
fn ($c) => str_starts_with($c, 'Swoole\\') || str_starts_with($c, 'OpenSwoole\\')
)));
php artisan octane:install --server=... regenerates it) and fully restart workers — stale workers will hold old symbol tables.class_alias list; if it's small, vendoring it as bootstrap/swoole-compat.php removes a supply-chain hop and gives you control over which classes are mapped.Swoole\* classes, ensure the alias bootstrap runs during preload compilation, or preload the OpenSwoole\* names instead.How can I help you explore Laravel packages today?