The error you have is because the SCM (Windows service manager) tries to communicate with the service, to know it's state ("I'm starting", "I have started", "I'm running"…) or send orders ("please pause", "please stop")
You have to manage those exchanges, which can be done with the PECL win32service extension.
I first read about it here: https://stackoverflow.com/a/8703371/913967
My answer is for Windows only. On Linux, you would instead intercept OS signals like SIGINT and SIGTERM.
Important
As this is a Windows extension, you should always check if the functions are available for your current env. For exemple, with:
if ( function_exists('win32_start_service_ctrl_dispatcher') ) {
// OK, I'm on Windows I can execute win32_xxx functions
}
To init the service
// Start the dialog with the SCM
win32_start_service_ctrl_dispatcher('myservice');
// If our program takes a long time before being ready,
// we can tell the SCM that we are starting (unnecessary if it's fast)
win32_set_service_status(WIN32_SERVICE_START_PENDING);
// OK, we are ready
win32_set_service_status(WIN32_SERVICE_RUNNING);
To stop the service
At the end of your program, just before the exit()
or whatever, indicate that the service is down:
win32_set_service_status(WIN32_SERVICE_STOP);
To intercept SCM messages and do what they ask for
You must periodically listen for messages, to know if you must do something. This is done by:
$scmAction = win32_get_last_control_message();
This can be added in an infinite loop or whatever (activated by a timer...)
You can then process the given order, and notify the SCM when it is done (so that the SCM knows you did what he asked)
For example:
switch ($scmAction) {
// Asked to stop
case WIN32_SERVICE_CONTROL_SHUTDOWN:
case WIN32_SERVICE_CONTROL_STOP:
win32_set_service_status(WIN32_SERVICE_STOP_PENDING);
// ...
// do your stuff here to stop your service
// ...
win32_set_service_status(WIN32_SERVICE_STOPPED);
break;
// Asked to pause
case \WIN32_SERVICE_CONTROL_PAUSE:
// ...
// do your stuff here to pause your service
// (for example: keep your infinite loop, but do not process things in it)
// ...
\win32_set_service_status(\WIN32_SERVICE_PAUSED);
break;
// Asked to continue (after a pause)
case \WIN32_SERVICE_CONTROL_CONTINUE:
// ...
// do your stuff here to reactivate your service
// ...
\win32_set_service_status(\WIN32_SERVICE_RUNNING);
break;
// Asked for our current state
case \WIN32_SERVICE_CONTROL_INTERROGATE:
$state = $paused ? WIN32_SERVICE_PAUSED : WIN32_SERVICE_RUNNING;
\win32_set_service_status($state);
}
Install your script as a service
This can be done using the win32service extension, or manually with sc.exe
Using the win32service extension
Can be interesting if you want to manage installation/removal of your service in PHP. For example, if you want to implement some command line argument like:
php my_script.php --install
php my_script.php --remove
To add a service
(adjust names and script address)
$result = win32_create_service([
'service' => 'myservice',
'display' => 'My PHP Service',
'path' => 'php.exe',
'params' => __DIR__.'\\my-service.php',
]);
if ($result != WIN32_NO_ERROR) {
// Something went wrong
// For the error explanation, compare $result with the error codes constants
// in https://www.php.net/manual/en/win32service.constants.errors.php
}
Note that we do NOT define a path for php.exe
here... so this one has to be found in your Windows PATH
env. variable. Or specify the absolute address if you know it and you are sure it will not change on your system.
To remove the service:
$result = win32_delete_service('myservice');
if ($result != WIN32_NO_ERROR) {
// Same as above
}
Using sc.exe
For usage help: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/sc-create
Note: you must have admin rights when executing those commands.
To install the service
sc create myservice type= own start= auto binpath= "php.exe -f C:\Path\to\my\php-script.php" displayname= "My PHP Service"
(As before, I didn't set the path for php.exe
here. Add it if it is not in your PATH
env. variable.
To remove the service
sc delete myservice
Test the service
Have to be done with admin rights
Start the service:
net start myservice
or:
sc start myservice
Stop the service:
net stop myservice
or:
sc stop myservice
Query service state:
sc query myservice
Ask to pause / resume:
sc pause myservice
sc continue myservice
(Do a sc query
again after to know the state)