Correct Aphlict websocket URI construction after PHP8 compatibility changes
[phabricator.git] / src / applications / config / check / PhabricatorSetupCheck.php
blob67074164241e7112d6567a47fb338665bb4dd6ed
1 <?php
3 abstract class PhabricatorSetupCheck extends Phobject {
5 private $issues;
7 abstract protected function executeChecks();
9 const GROUP_OTHER = 'other';
10 const GROUP_MYSQL = 'mysql';
11 const GROUP_PHP = 'php';
12 const GROUP_IMPORTANT = 'important';
14 public function getExecutionOrder() {
15 if ($this->isPreflightCheck()) {
16 return 0;
17 } else {
18 return 1000;
22 /**
23 * Should this check execute before we load configuration?
25 * The majority of checks (particularly, those checks which examine
26 * configuration) should run in the normal setup phase, after configuration
27 * loads. However, a small set of critical checks (mostly, tests for PHP
28 * setup and extensions) need to run before we can load configuration.
30 * @return bool True to execute before configuration is loaded.
32 public function isPreflightCheck() {
33 return false;
36 final protected function newIssue($key) {
37 $issue = id(new PhabricatorSetupIssue())
38 ->setIssueKey($key);
39 $this->issues[$key] = $issue;
41 if ($this->getDefaultGroup()) {
42 $issue->setGroup($this->getDefaultGroup());
45 return $issue;
48 final public function getIssues() {
49 return $this->issues;
52 protected function addIssue(PhabricatorSetupIssue $issue) {
53 $this->issues[$issue->getIssueKey()] = $issue;
54 return $this;
57 public function getDefaultGroup() {
58 return null;
61 final public function runSetupChecks() {
62 $this->issues = array();
63 $this->executeChecks();
66 final public static function getOpenSetupIssueKeys() {
67 $cache = PhabricatorCaches::getSetupCache();
68 return $cache->getKey('phabricator.setup.issue-keys');
71 final public static function resetSetupState() {
72 $cache = PhabricatorCaches::getSetupCache();
73 $cache->deleteKey('phabricator.setup.issue-keys');
75 $server_cache = PhabricatorCaches::getServerStateCache();
76 $server_cache->deleteKey('phabricator.in-flight');
78 $use_scope = AphrontWriteGuard::isGuardActive();
79 if ($use_scope) {
80 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
81 } else {
82 AphrontWriteGuard::allowDangerousUnguardedWrites(true);
85 try {
86 $db_cache = new PhabricatorKeyValueDatabaseCache();
87 $db_cache->deleteKey('phabricator.setup.issue-keys');
88 } catch (Exception $ex) {
89 // If we hit an exception here, just ignore it. In particular, this can
90 // happen on initial startup before the databases are initialized.
93 if ($use_scope) {
94 unset($unguarded);
95 } else {
96 AphrontWriteGuard::allowDangerousUnguardedWrites(false);
100 final public static function setOpenSetupIssueKeys(
101 array $keys,
102 $update_database) {
103 $cache = PhabricatorCaches::getSetupCache();
104 $cache->setKey('phabricator.setup.issue-keys', $keys);
106 $server_cache = PhabricatorCaches::getServerStateCache();
107 $server_cache->setKey('phabricator.in-flight', 1);
109 if ($update_database) {
110 $db_cache = new PhabricatorKeyValueDatabaseCache();
111 try {
112 $json = phutil_json_encode($keys);
113 $db_cache->setKey('phabricator.setup.issue-keys', $json);
114 } catch (Exception $ex) {
115 // Ignore any write failures, since they likely just indicate that we
116 // have a database-related setup issue that needs to be resolved.
121 final public static function getOpenSetupIssueKeysFromDatabase() {
122 $db_cache = new PhabricatorKeyValueDatabaseCache();
123 try {
124 $value = $db_cache->getKey('phabricator.setup.issue-keys');
125 if ($value === null || !strlen($value)) {
126 return null;
128 return phutil_json_decode($value);
129 } catch (Exception $ex) {
130 return null;
134 final public static function getUnignoredIssueKeys(array $all_issues) {
135 assert_instances_of($all_issues, 'PhabricatorSetupIssue');
136 $keys = array();
137 foreach ($all_issues as $issue) {
138 if (!$issue->getIsIgnored()) {
139 $keys[] = $issue->getIssueKey();
142 return $keys;
145 final public static function getConfigNeedsRepair() {
146 $cache = PhabricatorCaches::getSetupCache();
147 return $cache->getKey('phabricator.setup.needs-repair');
150 final public static function setConfigNeedsRepair($needs_repair) {
151 $cache = PhabricatorCaches::getSetupCache();
152 $cache->setKey('phabricator.setup.needs-repair', $needs_repair);
155 final public static function deleteSetupCheckCache() {
156 $cache = PhabricatorCaches::getSetupCache();
157 $cache->deleteKeys(
158 array(
159 'phabricator.setup.needs-repair',
160 'phabricator.setup.issue-keys',
164 final public static function willPreflightRequest() {
165 $checks = self::loadAllChecks();
167 foreach ($checks as $check) {
168 if (!$check->isPreflightCheck()) {
169 continue;
172 $check->runSetupChecks();
174 foreach ($check->getIssues() as $key => $issue) {
175 return self::newIssueResponse($issue);
179 return null;
182 public static function newIssueResponse(PhabricatorSetupIssue $issue) {
183 $view = id(new PhabricatorSetupIssueView())
184 ->setIssue($issue);
186 return id(new PhabricatorConfigResponse())
187 ->setView($view);
190 final public static function willProcessRequest() {
191 $issue_keys = self::getOpenSetupIssueKeys();
192 if ($issue_keys === null) {
193 $engine = new PhabricatorSetupEngine();
194 $response = $engine->execute();
195 if ($response) {
196 return $response;
198 } else if ($issue_keys) {
199 // If Phabricator is configured in a cluster with multiple web devices,
200 // we can end up with setup issues cached on every device. This can cause
201 // a warning banner to show on every device so that each one needs to
202 // be dismissed individually, which is pretty annoying. See T10876.
204 // To avoid this, check if the issues we found have already been cleared
205 // in the database. If they have, we'll just wipe out our own cache and
206 // move on.
207 $issue_keys = self::getOpenSetupIssueKeysFromDatabase();
208 if ($issue_keys !== null) {
209 self::setOpenSetupIssueKeys($issue_keys, $update_database = false);
213 // Try to repair configuration unless we have a clean bill of health on it.
214 // We need to keep doing this on every page load until all the problems
215 // are fixed, which is why it's separate from setup checks (which run
216 // once per restart).
217 $needs_repair = self::getConfigNeedsRepair();
218 if ($needs_repair !== false) {
219 $needs_repair = self::repairConfig();
220 self::setConfigNeedsRepair($needs_repair);
225 * Test if we've survived through setup on at least one normal request
226 * without fataling.
228 * If we've made it through setup without hitting any fatals, we switch
229 * to render a more friendly error page when encountering issues like
230 * database connection failures. This gives users a smoother experience in
231 * the face of intermittent failures.
233 * @return bool True if we've made it through setup since the last restart.
235 final public static function isInFlight() {
236 $cache = PhabricatorCaches::getServerStateCache();
237 return (bool)$cache->getKey('phabricator.in-flight');
240 final public static function loadAllChecks() {
241 return id(new PhutilClassMapQuery())
242 ->setAncestorClass(__CLASS__)
243 ->setSortMethod('getExecutionOrder')
244 ->execute();
247 final public static function runNormalChecks() {
248 $checks = self::loadAllChecks();
250 foreach ($checks as $key => $check) {
251 if ($check->isPreflightCheck()) {
252 unset($checks[$key]);
256 $issues = array();
257 foreach ($checks as $check) {
258 $check->runSetupChecks();
259 foreach ($check->getIssues() as $key => $issue) {
260 if (isset($issues[$key])) {
261 throw new Exception(
262 pht(
263 "Two setup checks raised an issue with key '%s'!",
264 $key));
266 $issues[$key] = $issue;
267 if ($issue->getIsFatal()) {
268 break 2;
273 $ignore_issues = PhabricatorEnv::getEnvConfig('config.ignore-issues');
274 foreach ($ignore_issues as $ignorable => $derp) {
275 if (isset($issues[$ignorable])) {
276 $issues[$ignorable]->setIsIgnored(true);
280 return $issues;
283 final public static function repairConfig() {
284 $needs_repair = false;
286 $options = PhabricatorApplicationConfigOptions::loadAllOptions();
287 foreach ($options as $option) {
288 try {
289 $option->getGroup()->validateOption(
290 $option,
291 PhabricatorEnv::getEnvConfig($option->getKey()));
292 } catch (PhabricatorConfigValidationException $ex) {
293 PhabricatorEnv::repairConfig($option->getKey(), $option->getDefault());
294 $needs_repair = true;
298 return $needs_repair;