Ian province abbreviation patch - issue 724
[civicrm-core.git] / Civi / API / ExternalBatch.php
1 <?php
2 namespace Civi\API;
3
4 use Symfony\Component\Process\PhpExecutableFinder;
5 use Symfony\Component\Process\Process;
6
7 /**
8 * Class ExternalBatch
9 * @package Civi\API
10 *
11 * Perform a series of external, asynchronous, concurrent API call.
12 */
13 class ExternalBatch {
14 /**
15 * The time to wait when polling for process status (microseconds).
16 */
17 const POLL_INTERVAL = 10000;
18
19 /**
20 * @var array
21 * Array(int $idx => array $apiCall).
22 */
23 protected $apiCalls;
24
25 protected $defaultParams;
26
27 protected $root;
28
29 protected $settingsPath;
30
31 protected $env = array();
32
33 /**
34 * @var array
35 * Array(int $idx => Process $process).
36 */
37 protected $processes;
38
39 /**
40 * @var array
41 * Array(int $idx => array $apiResult).
42 */
43 protected $apiResults;
44
45 /**
46 * @param array $defaultParams
47 * Default values to merge into any API calls.
48 */
49 public function __construct($defaultParams = array()) {
50 global $civicrm_root;
51 $this->root = $civicrm_root;
52 $this->settingsPath = defined('CIVICRM_SETTINGS_PATH') ? CIVICRM_SETTINGS_PATH : NULL;
53 $this->defaultParams = $defaultParams;
54 }
55
56 /**
57 * @param string $entity
58 * @param string $action
59 * @param array $params
60 * @return $this
61 */
62 public function addCall($entity, $action, $params = array()) {
63 $params = array_merge($this->defaultParams, $params);
64
65 $this->apiCalls[] = array(
66 'entity' => $entity,
67 'action' => $action,
68 'params' => $params,
69 );
70 return $this;
71 }
72
73 /**
74 * @param array $env
75 * List of environment variables to add.
76 * @return static
77 */
78 public function addEnv($env) {
79 $this->env = array_merge($this->env, $env);
80 return $this;
81 }
82
83 /**
84 * Run all the API calls concurrently.
85 *
86 * @return static
87 * @throws \CRM_Core_Exception
88 */
89 public function start() {
90 foreach ($this->apiCalls as $idx => $apiCall) {
91 $process = $this->createProcess($apiCall);
92 $process->start();
93 $this->processes[$idx] = $process;
94 }
95 return $this;
96 }
97
98 /**
99 * @return int
100 * The number of running processes.
101 */
102 public function getRunningCount() {
103 $count = 0;
104 foreach ($this->processes as $process) {
105 if ($process->isRunning()) {
106 $count++;
107 }
108 }
109 return $count;
110 }
111
112 public function wait() {
113 while (!empty($this->processes)) {
114 usleep(self::POLL_INTERVAL);
115 foreach (array_keys($this->processes) as $idx) {
116 /** @var Process $process */
117 $process = $this->processes[$idx];
118 if (!$process->isRunning()) {
119 $parsed = json_decode($process->getOutput(), TRUE);
120 if ($process->getExitCode() || $parsed === NULL) {
121 $this->apiResults[] = array(
122 'is_error' => 1,
123 'error_message' => 'External API returned malformed response.',
124 'trace' => array(
125 'code' => $process->getExitCode(),
126 'stdout' => $process->getOutput(),
127 'stderr' => $process->getErrorOutput(),
128 ),
129 );
130 }
131 else {
132 $this->apiResults[] = $parsed;
133 }
134 unset($this->processes[$idx]);
135 }
136 }
137 }
138 return $this;
139 }
140
141 /**
142 * @return array
143 */
144 public function getResults() {
145 return $this->apiResults;
146 }
147
148 /**
149 * @param int $idx
150 * @return array
151 */
152 public function getResult($idx = 0) {
153 return $this->apiResults[$idx];
154 }
155
156 /**
157 * Determine if the local environment supports running API calls
158 * externally.
159 *
160 * @return bool
161 */
162 public function isSupported() {
163 // If you try in Windows, feel free to change this...
164 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' || !function_exists('proc_open')) {
165 return FALSE;
166 }
167 if (!file_exists($this->root . '/bin/cli.php') || !file_exists($this->settingsPath)) {
168 return FALSE;
169 }
170 return TRUE;
171 }
172
173 /**
174 * @param array $apiCall
175 * Array with keys: entity, action, params.
176 * @return Process
177 * @throws \CRM_Core_Exception
178 */
179 public function createProcess($apiCall) {
180 $parts = array();
181
182 $executableFinder = new PhpExecutableFinder();
183 $php = $executableFinder->find();
184 if (!$php) {
185 throw new \CRM_Core_Exception("Failed to locate PHP interpreter.");
186 }
187 $parts[] = $php;
188
189 $parts[] = escapeshellarg($this->root . '/bin/cli.php');
190 $parts[] = escapeshellarg("-e=" . $apiCall['entity']);
191 $parts[] = escapeshellarg("-a=" . $apiCall['action']);
192 $parts[] = "--json";
193 $parts[] = escapeshellarg("-u=dummyuser");
194 foreach ($apiCall['params'] as $key => $value) {
195 $parts[] = escapeshellarg("--$key=$value");
196 }
197 $command = implode(" ", $parts);
198
199 $env = array_merge($this->env, array(
200 'CIVICRM_SETTINGS' => $this->settingsPath,
201 ));
202 return new Process($command, $this->root, $env);
203 }
204
205 /**
206 * @return string
207 */
208 public function getRoot() {
209 return $this->root;
210 }
211
212 /**
213 * @param string $root
214 */
215 public function setRoot($root) {
216 $this->root = $root;
217 }
218
219 /**
220 * @return string
221 */
222 public function getSettingsPath() {
223 return $this->settingsPath;
224 }
225
226 /**
227 * @param string $settingsPath
228 */
229 public function setSettingsPath($settingsPath) {
230 $this->settingsPath = $settingsPath;
231 }
232
233 }