@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add a generic multistep Markup cache

Summary:
The immediate issue this addresses is T1366, adding a rendering cache to Phriction. For wiki pages with code blocks especially, rerendering them each time is expensive.

The broader issue is that out markup caches aren't very good right now. They have three major problems:

**Problem 1: the data is stored in the wrong place.** We currently store remarkup caches on objects. This means we're always loading it and passing it around even when we don't need it, can't genericize cache management code (e.g., have one simple script to drop/GC caches), need to update authoritative rows to clear caches, and can't genericize rendering code since each object is different.

To solve this, I created a dedicated cache database that I plan to move all markup caches to use.

**Problem 2: time-variant rules break when cached.** Some rules like `**bold**` are time-invariant and always produce the same output, but some rules like `{Tnnn}` and `@username` are variant and may render differently (because a task was closed or a user is on vacation). Currently, we cache the raw output, so these time-variant rules get locked at whatever values they had when they were first rendered. This is the main reason Phriction doesn't have a cache right now -- I wanted `{Tnnn}` rules to reflect open/closed tasks.

To solve this, I split markup into a "preprocessing" phase (which does all the parsing and evaluates all time-invariant rules) and a "postprocessing" phase (which evaluates time-variant rules only). The preprocessing phase is most of the expense (and, notably, includes syntax highlighting) so this is nearly as good as caching the final output. I did most of the work here in D737 / D738, but we never moved to use it in Phabricator -- we currently just do the two operations serially in all cases.

This diff splits them apart and caches the output of preprocessing only, so we benefit from caching but also get accurate time-variant rendering.

**Problem 3: cache access isn't batched/pipelined optimally.** When we're rendering a list of markup blocks, we should be able to batch datafetching better than we do. D738 helped with this (fetching is batched within a single hunk of markup) and this improves batching on cache access. We could still do better here, but this is at least a step forward.

Also fixes a bug with generating a link in the Phriction history interface ($uri gets clobbered).

I'm using PHP serialization instead of JSON serialization because Remarkup does some stuff with non-ascii characters that might not survive JSON.

Test Plan:
- Created a Phriction document and verified that previews don't go to cache (no rows appear in the cache table).
- Verified that published documents come out of cache.
- Verified that caches generate/regenerate correctly, time-variant rules render properly and old documents hit the right caches.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T1366

Differential Revision: https://secure.phabricator.com/D2945

+533 -19
+1 -1
resources/sql/patches/harbormasterobject.sql
··· 4 4 name VARCHAR(255) COLLATE utf8_general_ci, 5 5 dateCreated INT UNSIGNED NOT NULL, 6 6 dateModified INT UNSIGNED NOT NULL 7 - ); 7 + ) ENGINE=InnoDB, COLLATE utf8_general_ci; 8 8 9 9 CREATE TABLE {$NAMESPACE}_harbormaster.edge ( 10 10 src VARCHAR(64) NOT NULL COLLATE utf8_bin,
+10
resources/sql/patches/markupcache.sql
··· 1 + CREATE TABLE {$NAMESPACE}_cache.cache_markupcache ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + cacheKey VARCHAR(128) NOT NULL collate utf8_bin, 4 + cacheData LONGTEXT NOT NULL COLLATE utf8_bin, 5 + metadata LONGTEXT NOT NULL COLLATE utf8_bin, 6 + dateCreated INT UNSIGNED NOT NULL, 7 + dateModified INT UNSIGNED NOT NULL, 8 + UNIQUE KEY (cacheKey), 9 + KEY (dateCreated) 10 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+10 -1
src/__phutil_library_map__.php
··· 563 563 'PhabricatorAuthController' => 'applications/auth/controller/PhabricatorAuthController.php', 564 564 'PhabricatorBaseEnglishTranslation' => 'infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php', 565 565 'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php', 566 + 'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php', 566 567 'PhabricatorCalendarBrowseController' => 'applications/calendar/controller/PhabricatorCalendarBrowseController.php', 567 568 'PhabricatorCalendarController' => 'applications/calendar/controller/PhabricatorCalendarController.php', 568 569 'PhabricatorCalendarDAO' => 'applications/calendar/storage/PhabricatorCalendarDAO.php', ··· 738 739 'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php', 739 740 'PhabricatorMailImplementationTestAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php', 740 741 'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php', 742 + 'PhabricatorMarkupCache' => 'applications/cache/storage/PhabricatorMarkupCache.php', 741 743 'PhabricatorMarkupEngine' => 'infrastructure/markup/PhabricatorMarkupEngine.php', 744 + 'PhabricatorMarkupInterface' => 'infrastructure/markup/PhabricatorMarkupInterface.php', 742 745 'PhabricatorMercurialGraphStream' => 'applications/repository/daemon/PhabricatorMercurialGraphStream.php', 743 746 'PhabricatorMetaMTAAttachment' => 'applications/metamta/storage/PhabricatorMetaMTAAttachment.php', 744 747 'PhabricatorMetaMTAController' => 'applications/metamta/controller/PhabricatorMetaMTAController.php', ··· 1590 1593 'PhabricatorAuthController' => 'PhabricatorController', 1591 1594 'PhabricatorBaseEnglishTranslation' => 'PhabricatorTranslation', 1592 1595 'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList', 1596 + 'PhabricatorCacheDAO' => 'PhabricatorLiskDAO', 1593 1597 'PhabricatorCalendarBrowseController' => 'PhabricatorCalendarController', 1594 1598 'PhabricatorCalendarController' => 'PhabricatorController', 1595 1599 'PhabricatorCalendarDAO' => 'PhabricatorLiskDAO', ··· 1742 1746 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', 1743 1747 'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter', 1744 1748 'PhabricatorMailImplementationTestAdapter' => 'PhabricatorMailImplementationAdapter', 1749 + 'PhabricatorMarkupCache' => 'PhabricatorCacheDAO', 1745 1750 'PhabricatorMetaMTAController' => 'PhabricatorController', 1746 1751 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO', 1747 1752 'PhabricatorMetaMTAEmailBodyParserTestCase' => 'PhabricatorTestCase', ··· 2037 2042 'PhrictionDAO' => 'PhabricatorLiskDAO', 2038 2043 'PhrictionDeleteController' => 'PhrictionController', 2039 2044 'PhrictionDiffController' => 'PhrictionController', 2040 - 'PhrictionDocument' => 'PhrictionDAO', 2045 + 'PhrictionDocument' => 2046 + array( 2047 + 0 => 'PhrictionDAO', 2048 + 1 => 'PhabricatorMarkupInterface', 2049 + ), 2041 2050 'PhrictionDocumentController' => 'PhrictionController', 2042 2051 'PhrictionDocumentPreviewController' => 'PhrictionController', 2043 2052 'PhrictionDocumentStatus' => 'PhrictionConstants',
+25
src/applications/cache/storage/PhabricatorCacheDAO.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + abstract class PhabricatorCacheDAO extends PhabricatorLiskDAO { 20 + 21 + public function getApplicationName() { 22 + return 'cache'; 23 + } 24 + 25 + }
+34
src/applications/cache/storage/PhabricatorMarkupCache.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class PhabricatorMarkupCache extends PhabricatorCacheDAO { 20 + 21 + protected $cacheKey; 22 + protected $cacheData; 23 + protected $metadata; 24 + 25 + public function getConfiguration() { 26 + return array( 27 + self::CONFIG_SERIALIZATION => array( 28 + 'cacheData' => self::SERIALIZATION_PHP, 29 + 'metadata' => self::SERIALIZATION_JSON, 30 + ), 31 + ) + parent::getConfiguration(); 32 + } 33 + 34 + }
+2 -2
src/applications/phriction/controller/PhrictionHistoryController.php
··· 61 61 $rows = array(); 62 62 foreach ($history as $content) { 63 63 64 - $uri = PhrictionDocument::getSlugURI($document->getSlug()); 64 + $slug_uri = PhrictionDocument::getSlugURI($document->getSlug()); 65 65 $version = $content->getVersion(); 66 66 67 67 $diff_uri = new PhutilURI('/phriction/diff/'.$document->getID().'/'); ··· 102 102 phutil_render_tag( 103 103 'a', 104 104 array( 105 - 'href' => $uri.'?v='.$version, 105 + 'href' => $slug_uri.'?v='.$version, 106 106 ), 107 107 'Version '.$version), 108 108 $handles[$content->getAuthorPHID()]->renderLink(),
+62 -4
src/applications/phriction/storage/PhrictionContent.php
··· 17 17 */ 18 18 19 19 /** 20 + * @task markup Markup Interface 21 + * 20 22 * @group phriction 21 23 */ 22 - final class PhrictionContent extends PhrictionDAO { 24 + final class PhrictionContent extends PhrictionDAO 25 + implements PhabricatorMarkupInterface { 26 + 27 + const MARKUP_FIELD_BODY = 'markup:body'; 23 28 24 29 protected $id; 25 30 protected $documentID; ··· 35 40 protected $changeRef; 36 41 37 42 public function renderContent() { 38 - $engine = PhabricatorMarkupEngine::newPhrictionMarkupEngine(); 39 - $markup = $engine->markupText($this->getContent()); 43 + return PhabricatorMarkupEngine::renderOneObject( 44 + $this, 45 + self::MARKUP_FIELD_BODY); 46 + } 47 + 48 + 49 + /* -( Markup Interface )--------------------------------------------------- */ 50 + 51 + 52 + /** 53 + * @task markup 54 + */ 55 + public function getMarkupFieldKey($field) { 56 + if ($this->shouldUseMarkupCache($field)) { 57 + $id = $this->getID(); 58 + } else { 59 + $id = PhabricatorHash::digest($this->getMarkupText($field)); 60 + } 61 + return "phriction:{$field}:{$id}"; 62 + } 63 + 64 + 65 + /** 66 + * @task markup 67 + */ 68 + public function getMarkupText($field) { 69 + return $this->getContent(); 70 + } 71 + 72 + 73 + /** 74 + * @task markup 75 + */ 76 + public function newMarkupEngine($field) { 77 + return PhabricatorMarkupEngine::newPhrictionMarkupEngine(); 78 + } 79 + 80 + 81 + /** 82 + * @task markup 83 + */ 84 + public function didMarkupText( 85 + $field, 86 + $output, 87 + PhutilMarkupEngine $engine) { 40 88 41 89 $toc = PhutilRemarkupEngineRemarkupHeaderBlockRule::renderTableOfContents( 42 90 $engine); 91 + 43 92 if ($toc) { 44 93 $toc = 45 94 '<div class="phabricator-remarkup-toc">'. ··· 53 102 return 54 103 '<div class="phabricator-remarkup">'. 55 104 $toc. 56 - $markup. 105 + $output. 57 106 '</div>'; 58 107 } 108 + 109 + 110 + /** 111 + * @task markup 112 + */ 113 + public function shouldUseMarkupCache($field) { 114 + return (bool)$this->getID(); 115 + } 116 + 59 117 60 118 }
+278 -11
src/infrastructure/markup/PhabricatorMarkupEngine.php
··· 16 16 * limitations under the License. 17 17 */ 18 18 19 - class PhabricatorMarkupEngine { 19 + /** 20 + * Manages markup engine selection, configuration, application, caching and 21 + * pipelining. 22 + * 23 + * @{class:PhabricatorMarkupEngine} can be used to render objects which 24 + * implement @{interface:PhabricatorMarkupInterface} in a batched, cache-aware 25 + * way. For example, if you have a list of comments written in remarkup (and 26 + * the objects implement the correct interface) you can render them by first 27 + * building an engine and adding the fields with @{method:addObject}. 28 + * 29 + * $field = 'field:body'; // Field you want to render. Each object exposes 30 + * // one or more fields of markup. 31 + * 32 + * $engine = new PhabricatorMarkupEngine(); 33 + * foreach ($comments as $comment) { 34 + * $engine->addObject($comment, $field); 35 + * } 36 + * 37 + * Now, call @{method:process} to perform the actual cache/rendering 38 + * step. This is a heavyweight call which does batched data access and 39 + * transforms the markup into output. 40 + * 41 + * $engine->process(); 42 + * 43 + * Finally, do something with the results: 44 + * 45 + * $results = array(); 46 + * foreach ($comments as $comment) { 47 + * $results[] = $engine->getOutput($comment, $field); 48 + * } 49 + * 50 + * If you have a single object to render, you can use the convenience method 51 + * @{method:renderOneObject}. 52 + * 53 + * @task markup Markup Pipeline 54 + * @task engine Engine Construction 55 + */ 56 + final class PhabricatorMarkupEngine { 57 + 58 + private $objects = array(); 59 + 60 + 61 + /* -( Markup Pipeline )---------------------------------------------------- */ 62 + 63 + 64 + /** 65 + * Convenience method for pushing a single object through the markup 66 + * pipeline. 67 + * 68 + * @param PhabricatorMarkupInterface The object to render. 69 + * @param string The field to render. 70 + * @return string Marked up output. 71 + * @task markup 72 + */ 73 + public static function renderOneObject( 74 + PhabricatorMarkupInterface $object, 75 + $field) { 76 + return id(new PhabricatorMarkupEngine()) 77 + ->addObject($object, $field) 78 + ->process() 79 + ->getOutput($object, $field); 80 + } 81 + 82 + 83 + /** 84 + * Queue an object for markup generation when @{method:process} is 85 + * called. You can retrieve the output later with @{method:getOutput}. 86 + * 87 + * @param PhabricatorMarkupInterface The object to render. 88 + * @param string The field to render. 89 + * @return this 90 + * @task markup 91 + */ 92 + public function addObject(PhabricatorMarkupInterface $object, $field) { 93 + $key = $this->getMarkupFieldKey($object, $field); 94 + $this->objects[$key] = array( 95 + 'object' => $object, 96 + 'field' => $field, 97 + ); 98 + 99 + return $this; 100 + } 101 + 102 + 103 + /** 104 + * Process objects queued with @{method:addObject}. You can then retrieve 105 + * the output with @{method:getOutput}. 106 + * 107 + * @return this 108 + * @task markup 109 + */ 110 + public function process() { 111 + $keys = array(); 112 + foreach ($this->objects as $key => $info) { 113 + if (!isset($info['markup'])) { 114 + $keys[] = $key; 115 + } 116 + } 117 + 118 + if (!$keys) { 119 + return; 120 + } 121 + 122 + $objects = array_select_keys($this->objects, $keys); 123 + 124 + // Build all the markup engines. We need an engine for each field whether 125 + // we have a cache or not, since we still need to postprocess the cache. 126 + $engines = array(); 127 + foreach ($objects as $key => $info) { 128 + $engines[$key] = $info['object']->newMarkupEngine($info['field']); 129 + } 130 + 131 + // Load or build the preprocessor caches. 132 + $blocks = $this->loadPreprocessorCaches($engines, $objects); 133 + 134 + // Finalize the output. 135 + foreach ($objects as $key => $info) { 136 + $data = $blocks[$key]->getCacheData(); 137 + $engine = $engines[$key]; 138 + $field = $info['field']; 139 + $object = $info['object']; 140 + 141 + $output = $engine->postprocessText($data); 142 + $output = $object->didMarkupText($field, $output, $engine); 143 + $this->objects[$key]['output'] = $output; 144 + } 145 + 146 + return $this; 147 + } 148 + 149 + 150 + /** 151 + * Get the output of markup processing for a field queued with 152 + * @{method:addObject}. Before you can call this method, you must call 153 + * @{method:process}. 154 + * 155 + * @param PhabricatorMarkupInterface The object to retrieve. 156 + * @param string The field to retrieve. 157 + * @return string Processed output. 158 + * @task markup 159 + */ 160 + public function getOutput(PhabricatorMarkupInterface $object, $field) { 161 + $key = $this->getMarkupFieldKey($object, $field); 162 + 163 + if (empty($this->objects[$key])) { 164 + throw new Exception( 165 + "Call addObject() before getOutput() (key = '{$key}')."); 166 + } 20 167 21 - public static function extractPHIDsFromMentions(array $content_blocks) { 22 - $mentions = array(); 168 + if (!isset($this->objects[$key]['output'])) { 169 + throw new Exception( 170 + "Call process() before getOutput()."); 171 + } 23 172 24 - $engine = self::newDifferentialMarkupEngine(); 173 + return $this->objects[$key]['output']; 174 + } 25 175 26 - foreach ($content_blocks as $content_block) { 27 - $engine->markupText($content_block); 28 - $phids = $engine->getTextMetadata( 29 - PhabricatorRemarkupRuleMention::KEY_MENTIONED, 30 - array()); 31 - $mentions += $phids; 176 + 177 + /** 178 + * @task markup 179 + */ 180 + private function getMarkupFieldKey( 181 + PhabricatorMarkupInterface $object, 182 + $field) { 183 + return $object->getMarkupFieldKey($field); 184 + } 185 + 186 + 187 + /** 188 + * @task markup 189 + */ 190 + private function loadPreprocessorCaches(array $engines, array $objects) { 191 + $blocks = array(); 192 + 193 + $use_cache = array(); 194 + foreach ($objects as $key => $info) { 195 + if ($info['object']->shouldUseMarkupCache($info['field'])) { 196 + $use_cache[$key] = true; 197 + } 32 198 } 33 199 34 - return $mentions; 200 + if ($use_cache) { 201 + $blocks = id(new PhabricatorMarkupCache())->loadAllWhere( 202 + 'cacheKey IN (%Ls)', 203 + array_keys($use_cache)); 204 + $blocks = mpull($blocks, null, 'getCacheKey'); 205 + } 206 + 207 + foreach ($objects as $key => $info) { 208 + if (isset($blocks[$key])) { 209 + // If we already have a preprocessing cache, we don't need to rebuild 210 + // it. 211 + continue; 212 + } 213 + 214 + $text = $info['object']->getMarkupText($info['field']); 215 + $data = $engines[$key]->preprocessText($text); 216 + 217 + // NOTE: This is just debugging information to help sort out cache issues. 218 + // If one machine is misconfigured and poisoning caches you can use this 219 + // field to hunt it down. 220 + 221 + $metadata = array( 222 + 'host' => php_uname('n'), 223 + ); 224 + 225 + $blocks[$key] = id(new PhabricatorMarkupCache()) 226 + ->setCacheKey($key) 227 + ->setCacheData($data) 228 + ->setMetadata($metadata); 229 + 230 + if (isset($use_cache[$key])) { 231 + // This is just filling a cache and always safe, even on a read pathway. 232 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 233 + try { 234 + $blocks[$key]->save(); 235 + } catch (AphrontQueryDuplicateKeyException $ex) { 236 + // Ignore this, we just raced to write the cache. 237 + } 238 + unset($unguarded); 239 + } 240 + } 241 + 242 + return $blocks; 35 243 } 36 244 245 + 246 + /* -( Engine Construction )------------------------------------------------ */ 247 + 248 + 249 + /** 250 + * @task engine 251 + */ 37 252 public static function newManiphestMarkupEngine() { 38 253 return self::newMarkupEngine(array( 39 254 )); 40 255 } 41 256 257 + 258 + /** 259 + * @task engine 260 + */ 42 261 public static function newPhrictionMarkupEngine() { 43 262 return self::newMarkupEngine(array( 44 263 'header.generate-toc' => true, 45 264 )); 46 265 } 47 266 267 + 268 + /** 269 + * @task engine 270 + */ 48 271 public static function newPhameMarkupEngine() { 49 272 return self::newMarkupEngine(array( 50 273 'macros' => false, 51 274 )); 52 275 } 53 276 277 + 278 + /** 279 + * @task engine 280 + */ 54 281 public static function newFeedMarkupEngine() { 55 282 return self::newMarkupEngine( 56 283 array( ··· 61 288 )); 62 289 } 63 290 291 + 292 + /** 293 + * @task engine 294 + */ 64 295 public static function newDifferentialMarkupEngine(array $options = array()) { 65 296 return self::newMarkupEngine(array( 66 297 'custom-inline' => PhabricatorEnv::getEnvConfig( ··· 71 302 )); 72 303 } 73 304 305 + 306 + /** 307 + * @task engine 308 + */ 74 309 public static function newDiffusionMarkupEngine(array $options = array()) { 75 310 return self::newMarkupEngine(array( 76 311 )); 77 312 } 78 313 314 + 315 + /** 316 + * @task engine 317 + */ 79 318 public static function newProfileMarkupEngine() { 80 319 return self::newMarkupEngine(array( 81 320 )); 82 321 } 83 322 323 + 324 + /** 325 + * @task engine 326 + */ 84 327 public static function newSlowvoteMarkupEngine() { 85 328 return self::newMarkupEngine(array( 86 329 )); 87 330 } 88 331 332 + 333 + /** 334 + * @task engine 335 + */ 89 336 private static function getMarkupEngineDefaultConfiguration() { 90 337 return array( 91 338 'pygments' => PhabricatorEnv::getEnvConfig('pygments.enabled'), ··· 104 351 ); 105 352 } 106 353 354 + 355 + /** 356 + * @task engine 357 + */ 107 358 private static function newMarkupEngine(array $options) { 108 359 109 360 $options += self::getMarkupEngineDefaultConfiguration(); ··· 196 447 $engine->setBlockRules($blocks); 197 448 198 449 return $engine; 450 + } 451 + 452 + public static function extractPHIDsFromMentions(array $content_blocks) { 453 + $mentions = array(); 454 + 455 + $engine = self::newDifferentialMarkupEngine(); 456 + 457 + foreach ($content_blocks as $content_block) { 458 + $engine->markupText($content_block); 459 + $phids = $engine->getTextMetadata( 460 + PhabricatorRemarkupRuleMention::KEY_MENTIONED, 461 + array()); 462 + $mentions += $phids; 463 + } 464 + 465 + return $mentions; 199 466 } 200 467 201 468 }
+103
src/infrastructure/markup/PhabricatorMarkupInterface.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + /** 20 + * An object which has one or more fields containing markup that can be 21 + * rendered into a display format. Commonly, the fields contain Remarkup and 22 + * are rendered into HTML. Implementing this interface allows you to render 23 + * objects through @{class:PhabricatorMarkupEngine} and benefit from caching 24 + * and pipelining infrastructure. 25 + * 26 + * An object may have several "fields" of markup. For example, Differential 27 + * revisions have a "summary" and a "test plan". In these cases, the `$field` 28 + * parameter is used to identify which field is being operated on. For simple 29 + * objects like comments, you might only have one field (say, "body"). In 30 + * these cases, the implementation can largely ignore the `$field` parameter. 31 + * 32 + * @task markup Markup Interface 33 + * 34 + * @group markup 35 + */ 36 + interface PhabricatorMarkupInterface { 37 + 38 + 39 + /* -( Markup Interface )--------------------------------------------------- */ 40 + 41 + 42 + /** 43 + * Get a key to identify this field. This should uniquely identify the block 44 + * of text to be rendered and be usable as a cache key. If the object has a 45 + * PHID, using the PHID and the field name is likley reasonable: 46 + * 47 + * "{$phid}:{$field}" 48 + * 49 + * @param string Field name. 50 + * @return string Cache key. 51 + * 52 + * @task markup 53 + */ 54 + public function getMarkupFieldKey($field); 55 + 56 + 57 + /** 58 + * Build the engine the field should use. 59 + * 60 + * @param string Field name. 61 + * @return PhutilRemarkupEngine Markup engine to use. 62 + * @task markup 63 + */ 64 + public function newMarkupEngine($field); 65 + 66 + 67 + /** 68 + * Return the contents of the specified field. 69 + * 70 + * @param string Field name. 71 + * @return string The raw markup contained in the field. 72 + * @task markup 73 + */ 74 + public function getMarkupText($field); 75 + 76 + 77 + /** 78 + * Callback for final postprocessing of output. Normally, you can return 79 + * the output unmodified. 80 + * 81 + * @param string Field name. 82 + * @param string The finalized output of the engine. 83 + * @param string The engine which generated the output. 84 + * @return string Final output. 85 + * @task markup 86 + */ 87 + public function didMarkupText( 88 + $field, 89 + $output, 90 + PhutilMarkupEngine $engine); 91 + 92 + 93 + /** 94 + * Determine if the engine should try to use the markup cache or not. 95 + * Generally you should use the cache for durable/permanent content but 96 + * should not use the cache for temporary/draft content. 97 + * 98 + * @return bool True to use the markup cache. 99 + * @task markup 100 + */ 101 + public function shouldUseMarkupCache($field); 102 + 103 + }
+8
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 155 155 'type' => 'db', 156 156 'name' => 'xhpastview', 157 157 ), 158 + 'db.cache' => array( 159 + 'type' => 'db', 160 + 'name' => 'cache', 161 + ), 158 162 '0000.legacy.sql' => array( 159 163 'type' => 'sql', 160 164 'name' => $this->getPatchPath('0000.legacy.sql'), ··· 902 906 'harbormasterobject.sql' => array( 903 907 'type' => 'sql', 904 908 'name' => $this->getPatchPath('harbormasterobject.sql'), 909 + ), 910 + 'markupcache.sql' => array( 911 + 'type' => 'sql', 912 + 'name' => $this->getPatchPath('markupcache.sql'), 905 913 ), 906 914 ); 907 915 }