|
| 1 | +--- |
| 2 | +name: async-soap-script |
| 3 | +description: Creates a ReactPHP async SOAP script in bin/async/ for the myadmin-hyperv-vps plugin. Use when user says 'async version', 'non-blocking', 'ReactPHP', or wants a bin/async/ variant of a SOAP operation. Uses React\EventLoop, React\Socket\Connector (TLS verify disabled), React\Http\Browser, Clue\React\Soap\Client and Proxy. Do NOT use for standard synchronous bin/ scripts — those follow a different pattern in bin/ directly. |
| 4 | +--- |
| 5 | +# async-soap-script |
| 6 | + |
| 7 | +Create a ReactPHP async SOAP script in `bin/async/` for a Hyper-V SOAP operation. |
| 8 | + |
| 9 | +## Critical |
| 10 | + |
| 11 | +- **Include path is one level deeper** than `bin/`: `__DIR__.'/../../../../../include/functions.inc.php'` (five `../` segments, not four). |
| 12 | +- **TLS verification must be disabled** on both the `React\Socket\Connector` (`verify_peer`, `verify_peer_name`) — the Hyper-V SOAP endpoint uses a self-signed cert. |
| 13 | +- **Never use `Plugin::getSoapClientParams()`** in async scripts — that is synchronous `SoapClient` config. The async path fetches the WSDL via `$browser->get()` and passes the body string to `Clue\React\Soap\Client`. |
| 14 | +- **Result property name** mirrors the method name: `CheckVMExists` → `$result->CheckVMExistsResult`. Derive it as `{MethodName}Result`. |
| 15 | +- The outer try/catch block wraps `get_service_master()` and the entire event-loop setup. The inner `->done()` error callback handles async failures separately. |
| 16 | + |
| 17 | +## Instructions |
| 18 | + |
| 19 | +1. **Identify the target SOAP method and its parameters.** |
| 20 | + - Find the equivalent synchronous script in `bin/` (e.g. `bin/CheckVMExists.php`) and note the method name and its parameter array. |
| 21 | + - Verify the synchronous script exists before proceeding. |
| 22 | + |
| 23 | +2. **Create the file inside `bin/async/`.** |
| 24 | + - Use the PascalCase SOAP method name, matching the synchronous counterpart exactly — e.g. `bin/async/TurnON.php` for the `TurnON` method, `bin/async/CheckVMExists.php` for `CheckVMExists`. |
| 25 | + |
| 26 | +3. **Write the shebang and bootstrap block** (copy verbatim): |
| 27 | + ```php |
| 28 | + #!/usr/bin/env php |
| 29 | + <?php |
| 30 | + include_once __DIR__.'/../../../../../include/functions.inc.php'; |
| 31 | + ini_set('soap.wsdl_cache_enabled', '0'); |
| 32 | + ini_set('default_socket_timeout', 1000); |
| 33 | + ini_set('max_input_time', '0'); |
| 34 | + ini_set('max_execution_time', '0'); |
| 35 | + ini_set('display_errors', '1'); |
| 36 | + ini_set('error_reporting', E_ALL); |
| 37 | + ``` |
| 38 | + |
| 39 | +4. **Write the argument count check.** Use the same `argc` minimum and die-message style as the sync script: |
| 40 | + ```php |
| 41 | + if ($_SERVER['argc'] < 3) { |
| 42 | + die("Call like {$_SERVER['argv'][0]} <id> <vps>\nwhere <id> is the VPS Master / Host Server ID\nuse 423 for Hyperv-dev and 440 for Hyperv1\n and <vps> is the id of a vps\n"); |
| 43 | + } |
| 44 | + ``` |
| 45 | + Adjust `< 3` and the message if the method takes fewer/more arguments (e.g. `GetVMList` only needs `<id>`, so `< 2`). |
| 46 | + |
| 47 | +5. **Write the async body inside a try/catch block:** |
| 48 | + ```php |
| 49 | + try { |
| 50 | + $master = get_service_master($_SERVER['argv'][1], 'vps', true); |
| 51 | + $loop = React\EventLoop\Factory::create(); |
| 52 | + $connector = new React\Socket\Connector(['tls' => ['verify_peer' => false, 'verify_peer_name' => false]]); |
| 53 | + $browser = new React\Http\Browser($connector, $loop); |
| 54 | + $wsdl = "https://{$master['vps_ip']}/HyperVService/HyperVService.asmx?WSDL"; |
| 55 | + $browser->get($wsdl)->done( |
| 56 | + function (Psr\Http\Message\ResponseInterface $response) use ($browser, $master) { |
| 57 | + $client = new Clue\React\Soap\Client($browser, (string)$response->getBody()); |
| 58 | + $proxy = new Clue\React\Soap\Proxy($client); |
| 59 | + $proxy->MethodName(['vmId' => $_SERVER['argv'][2], 'hyperVAdmin' => 'Administrator', 'adminPassword' => $master['vps_root']])->then(function ($result) { |
| 60 | + print_r($result->MethodNameResult); |
| 61 | + }); |
| 62 | + }, |
| 63 | + function (Exception $e) { |
| 64 | + echo 'Error: ' . $e->getMessage() . PHP_EOL; |
| 65 | + } |
| 66 | + ); |
| 67 | + $loop->run(); |
| 68 | + } catch (Exception $e) { |
| 69 | + echo 'Caught exception: '.$e->getMessage().PHP_EOL; |
| 70 | + } |
| 71 | + ``` |
| 72 | + - Replace `MethodName` with the actual SOAP method (e.g. `CheckVMExists`). |
| 73 | + - Replace `MethodNameResult` with `{MethodName}Result` (e.g. `CheckVMExistsResult`). |
| 74 | + - Replace the `$proxy->MethodName([...])` parameter array with the exact params from the sync script. |
| 75 | + - For methods that return a simple response (not a `*Result` property), use `print_r($result)` instead. |
| 76 | + |
| 77 | +6. **Make the file executable** (using `bin/async/TurnON.php` as the example): |
| 78 | + ```bash |
| 79 | + chmod +x bin/async/TurnON.php |
| 80 | + ``` |
| 81 | + |
| 82 | +7. **Verify the script runs without parse errors** (using `bin/async/TurnON.php` as the example): |
| 83 | + ```bash |
| 84 | + php -l bin/async/TurnON.php |
| 85 | + ``` |
| 86 | + |
| 87 | +## Examples |
| 88 | + |
| 89 | +**User says:** "Create an async version of TurnON" |
| 90 | + |
| 91 | +**Actions taken:** |
| 92 | +1. Read `bin/TurnON.php` — method is `TurnON`, params are `vmId`, `hyperVAdmin`, `adminPassword`, argc < 3. |
| 93 | +2. Create `bin/async/TurnON.php`: |
| 94 | + |
| 95 | +```php |
| 96 | +#!/usr/bin/env php |
| 97 | +<?php |
| 98 | +include_once __DIR__.'/../../../../../include/functions.inc.php'; |
| 99 | +ini_set('soap.wsdl_cache_enabled', '0'); |
| 100 | +ini_set('default_socket_timeout', 1000); |
| 101 | +ini_set('max_input_time', '0'); |
| 102 | +ini_set('max_execution_time', '0'); |
| 103 | +ini_set('display_errors', '1'); |
| 104 | +ini_set('error_reporting', E_ALL); |
| 105 | +if ($_SERVER['argc'] < 3) { |
| 106 | + die("Call like {$_SERVER['argv'][0]} <id> <vps>\nwhere <id> is the VPS Master / Host Server ID\nuse 423 for Hyperv-dev and 440 for Hyperv1\n and <vps> is the id of a vps\n"); |
| 107 | +} |
| 108 | +try { |
| 109 | + $master = get_service_master($_SERVER['argv'][1], 'vps', true); |
| 110 | + $loop = React\EventLoop\Factory::create(); |
| 111 | + $connector = new React\Socket\Connector(['tls' => ['verify_peer' => false, 'verify_peer_name' => false]]); |
| 112 | + $browser = new React\Http\Browser($connector, $loop); |
| 113 | + $wsdl = "https://{$master['vps_ip']}/HyperVService/HyperVService.asmx?WSDL"; |
| 114 | + $browser->get($wsdl)->done( |
| 115 | + function (Psr\Http\Message\ResponseInterface $response) use ($browser, $master) { |
| 116 | + $client = new Clue\React\Soap\Client($browser, (string)$response->getBody()); |
| 117 | + $proxy = new Clue\React\Soap\Proxy($client); |
| 118 | + $proxy->TurnON(['vmId' => $_SERVER['argv'][2], 'hyperVAdmin' => 'Administrator', 'adminPassword' => $master['vps_root']])->then(function ($result) { |
| 119 | + print_r($result); |
| 120 | + }); |
| 121 | + }, |
| 122 | + function (Exception $e) { |
| 123 | + echo 'Error: ' . $e->getMessage() . PHP_EOL; |
| 124 | + } |
| 125 | + ); |
| 126 | + $loop->run(); |
| 127 | +} catch (Exception $e) { |
| 128 | + echo 'Caught exception: '.$e->getMessage().PHP_EOL; |
| 129 | +} |
| 130 | +``` |
| 131 | + |
| 132 | +> Note: `TurnON` does not return a `TurnONResult` property — `print_r($result)` is used instead, matching the sync script. |
| 133 | +
|
| 134 | +**Result:** `bin/async/TurnON.php` created and executable. |
| 135 | + |
| 136 | +## Common Issues |
| 137 | + |
| 138 | +- **`include_once` fails with "No such file":** The async path needs five `../` (`__DIR__.'/../../../../../include/functions.inc.php'`), not four. Sync scripts in `bin/` use four. |
| 139 | + |
| 140 | +- **`Class 'React\EventLoop\Factory' not found`:** The ReactPHP packages are not installed. Run `composer install` and verify `react/event-loop`, `react/socket`, `react/http`, and `clue/reactphp-soap` are in `composer.json`. |
| 141 | + |
| 142 | +- **`Error: cURL error 60: SSL certificate problem`:** TLS options on the `Connector` are missing or wrong. Must be exactly `['tls' => ['verify_peer' => false, 'verify_peer_name' => false]]`. |
| 143 | + |
| 144 | +- **`Undefined property: $result->CheckVMExistsResult`:** Result property name is wrong. The convention is `{MethodName}Result`. Check the sync script — if it uses `print_r($response)` rather than `print_r($response->SomeResult)`, the method has no `*Result` property; use `print_r($result)` directly. |
| 145 | + |
| 146 | +- **Script exits immediately with no output:** `$loop->run()` was omitted or placed outside the try block. It must be the last statement inside try, after `$browser->get(...)->done(...)`. |
0 commit comments