Enable Event‑Loop in High‑Performance PHP Framework Webman via CLI
This article demonstrates a concise method to empower Webman command modules with event‑loop capabilities by extending the Worker class, creating an AbstractCommand base, and defining a concrete command that runs within the event‑loop, complete with runnable code examples and execution output.
When using Webman command modules, many developers discover that the command cannot leverage event‑loop features, and Workerman utilities such as AsyncTcpConnection require an event‑loop context. The following steps provide a minimal code solution to enable event‑loop support.
Step 1 : Create a Worker subclass that implements a one‑time execution method named once . The method runs the provided callable, stops all workers, and sends a SIGINT signal to the master process.
<?php
declare(strict_types=1);
namespace YOUR_NAMESPACE;
class Worker extends \Workerman\Worker
{
/**
* @param string|null $socketName
* @param array $socketContext
*/
public function __construct(?string $socketName = null, array $socketContext = [])
{
if (!extension_loaded('posix')) {
throw new \RuntimeException('Please install posix extension.');
}
if (!extension_loaded('pcntl')) {
throw new \RuntimeException('Please install pcntl extension.');
}
parent::__construct($socketName, $socketContext);
}
/**
* Run a one‑time process.
*
* @param string $name
* @param callable $run
* @param callable|null $stop
*/
public function once(string $name, callable $run, ?callable $stop = null): void
{
$this->name = $name;
$this->count = 1;
$this->onWorkerStart = function () use ($run) {
try {
$run();
} finally {
static::stopAll();
// Send signal to master to terminate
\posix_kill(\posix_getppid(), \SIGINT);
}
};
$this->onWorkerStop = $stop;
if (!$this->name) {
throw new \RuntimeException('Please set the name of the worker process through the name property.');
}
try {
static::checkSapiEnv();
static::initStdOut();
static::init();
static::checkPortAvailable();
static::lock();
static::daemonize();
static::initWorkers();
static::installSignal();
static::saveMasterPid();
static::lock(LOCK_UN);
static::forkWorkers();
static::resetStd();
static::monitorWorkers();
} catch (\Throwable $e) {
static::log($e);
}
}
}Step 2 : Implement an AbstractCommand class that works with ordinary CLI scripts. It defines abstract eventClass , helper methods info , error , success , and overrides run to create a Worker , configure PID and log files, assign the event‑loop, and invoke the one‑time process.
<?php
declare(strict_types=1);
namespace YOUR_NAMESPACE;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
abstract class AbstractCommand extends Command
{
/**
* Return the event‑loop class name; null means a normal CLI program.
*/
abstract protected function eventClass(): ?string;
/**
* Output an informational message.
*/
protected function info(OutputInterface $output, string $message): void
{
$output->writeln("ℹ️ $message");
}
/**
* Output an error message and return failure status.
*/
protected function error(OutputInterface $output, string $message): int
{
$output->writeln("❌ $message");
return self::FAILURE;
}
/**
* Output a success message and return success status.
*/
protected function success(OutputInterface $output, string $message): int
{
$output->writeln("✅ $message");
return self::SUCCESS;
}
public function run(InputInterface $input, OutputInterface $output): int
{
if ($event = $this->eventClass()) {
$result = 0;
$name = strtolower(str_replace('\\', '_', trim(get_called_class(), '\\')));
$worker = new Worker();
$worker::$pidFile = runtime_path() . "$name.pid";
$worker::$logFile = runtime_path() . "/logs/$name.log";
$worker->eventLoop = $event;
$worker->once($name, function () use ($input, $output, &$result) {
$result = parent::run($input, $output);
});
return $result;
}
return parent::run($input, $output);
}
}Step 3 : Create a concrete command class that extends AbstractCommand , implements eventClass to return Fiber::class , and writes the command logic in execute . The example builds a request body for a face‑embedding workflow, publishes it via workbunny/rabbitmq , and reports success.
<?php
declare(strict_types=1);
namespace app\command\Debug;
use app\packages\face\builders\GetFaceEmbeddingBuilder;
use app\packages\face\workflow\FacesEmbeddingLog;
use YOUR_NAMESPACE\AbstractCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Workerman\Events\Fiber;
use function Workbunny\WebmanRabbitMQ\publish;
class Test extends AbstractCommand
{
protected static $defaultName = 'debug:test';
protected static $defaultDescription = 'DEBUG';
protected function eventClass(): ?string
{
return Fiber::class;
}
protected function configure(): void
{
// No additional configuration needed
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$body = [
'image_url' => $url = 'http://www.xxx.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png', // image URL
'user_id' => $userId = -1, // user ID
'user_type' => $type = "test",
'prob_threshold' => 0.95,
'workflow' => [
[
'class' => FacesEmbeddingLog::class,
'params' => [
'userId' => $userId,
'userType' => $type,
'imageUrl' => $url,
'probThreshold' => 0.01,
],
],
],
];
// workbunny/rabbitmq depends on AsyncTcpConnection, must run in event‑loop
publish(new GetFaceEmbeddingBuilder(), GetFaceEmbeddingBuilder::encode($body));
return $this->success($output, 'success');
}
}Running the command with php webman debug:test starts the process inside the event‑loop and produces the following output:
root@ef2d58e20604:/var/www/face# php webman debug:test
✅ success
Workerman[webman] received signal SIGINT
Workerman[webman] stopping
Workerman[webman] has been stoppedThanks to the original author for the share (source: https://www.workerman.net/a/2004).
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Open Source Tech Hub
Sharing cutting-edge internet technologies and practical AI resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
