4 use Symfony\Component\Process\PhpExecutableFinder
;
5 use Symfony\Component\Process\Process
;
11 * Perform a series of external, asynchronous, concurrent API call.
15 * The time to wait when polling for process status (microseconds).
17 const POLL_INTERVAL
= 10000;
21 * Array(int $idx => array $apiCall).
25 protected $defaultParams;
29 protected $settingsPath;
35 * Array(int $idx => Process $process).
41 * Array(int $idx => array $apiResult).
43 protected $apiResults;
46 * @param array $defaultParams
47 * Default values to merge into any API calls.
49 public function __construct($defaultParams = array()) {
51 $this->root
= $civicrm_root;
52 $this->settingsPath
= defined('CIVICRM_SETTINGS_PATH') ? CIVICRM_SETTINGS_PATH
: NULL;
53 $this->defaultParams
= $defaultParams;
58 * @param string $entity
59 * @param string $action
60 * @param array $params
61 * @return ExternalBatch
63 public function addCall($entity, $action, $params = array()) {
64 $params = array_merge($this->defaultParams
, $params);
66 $this->apiCalls
[] = array(
76 * List of environment variables to add.
79 public function addEnv($env) {
80 $this->env
= array_merge($this->env
, $env);
85 * Run all the API calls concurrently.
88 * @throws \CRM_Core_Exception
90 public function start() {
91 foreach ($this->apiCalls
as $idx => $apiCall) {
92 $process = $this->createProcess($apiCall);
94 $this->processes
[$idx] = $process;
101 * The number of running processes.
103 public function getRunningCount() {
105 foreach ($this->processes
as $process) {
106 if ($process->isRunning()) {
113 public function wait() {
114 while (!empty($this->processes
)) {
115 usleep(self
::POLL_INTERVAL
);
116 foreach (array_keys($this->processes
) as $idx) {
117 /** @var Process $process */
118 $process = $this->processes
[$idx];
119 if (!$process->isRunning()) {
120 $parsed = json_decode($process->getOutput(), TRUE);
121 if ($process->getExitCode() ||
$parsed === NULL) {
122 $this->apiResults
[] = array(
124 'error_message' => 'External API returned malformed response.',
126 'code' => $process->getExitCode(),
127 'stdout' => $process->getOutput(),
128 'stderr' => $process->getErrorOutput(),
133 $this->apiResults
[] = $parsed;
135 unset($this->processes
[$idx]);
145 public function getResults() {
146 return $this->apiResults
;
153 public function getResult($idx = 0) {
154 return $this->apiResults
[$idx];
158 * Determine if the local environment supports running API calls
163 public function isSupported() {
164 // If you try in Windows, feel free to change this...
165 if (strtoupper(substr(PHP_OS
, 0, 3)) === 'WIN' ||
!function_exists('proc_open')) {
168 if (!file_exists($this->root
. '/bin/cli.php') ||
!file_exists($this->settingsPath
)) {
175 * @param array $apiCall
176 * Array with keys: entity, action, params.
178 * @throws \CRM_Core_Exception
180 public function createProcess($apiCall) {
183 if (defined('CIVICRM_TEST') && CIVICRM_TEST
) {
184 // When testing, civicrm.settings.php may rely on $_CV, which is only
185 // populated/propagated if we execute through `cv`.
187 $parts[] = escapeshellarg($apiCall['entity'] . '.' . $apiCall['action']);
188 $parts[] = "--out=json-strict";
189 foreach ($apiCall['params'] as $key => $value) {
190 $parts[] = escapeshellarg("$key=$value");
194 // But in production, we may not have `cv` installed.
195 $executableFinder = new PhpExecutableFinder();
196 $php = $executableFinder->find();
198 throw new \
CRM_Core_Exception("Failed to locate PHP interpreter.");
201 $parts[] = escapeshellarg($this->root
. '/bin/cli.php');
202 $parts[] = escapeshellarg("-e=" . $apiCall['entity']);
203 $parts[] = escapeshellarg("-a=" . $apiCall['action']);
205 $parts[] = escapeshellarg("-u=dummyuser");
206 foreach ($apiCall['params'] as $key => $value) {
207 $parts[] = escapeshellarg("--$key=$value");
211 $command = implode(" ", $parts);
212 $env = array_merge($this->env
, array(
213 'CIVICRM_SETTINGS' => $this->settingsPath
,
215 return new Process($command, $this->root
, $env);
221 public function getRoot() {
226 * @param string $root
228 public function setRoot($root) {
235 public function getSettingsPath() {
236 return $this->settingsPath
;
240 * @param string $settingsPath
242 public function setSettingsPath($settingsPath) {
243 $this->settingsPath
= $settingsPath;