|
| 1 | +# Magento 2 Staging |
| 2 | + |
| 3 | +## High level staging implementation overview |
| 4 | + |
| 5 | +- Future editions of products/categories etc. are saved as a copy with a timestamped from-to columns. |
| 6 | +- Current staging version is saved as a flag, used in all collections to add WHERE condition |
| 7 | +- You can browse (preview) frontend site seeing future versions passing version and signature GET parameters |
| 8 | + |
| 9 | + |
| 10 | +## Demonstrate an understanding of the events processing flow |
| 11 | + |
| 12 | +Influence of Staging on the event processing. |
| 13 | + |
| 14 | +What is a modification of the event processing mechanism introduced by the staging module? |
| 15 | + |
| 16 | + |
| 17 | +\Magento\Staging\Model\Event\Manager: |
| 18 | + |
| 19 | +- ban some events in preview mode - none by default |
| 20 | +- ban some observers in preview mode |
| 21 | + |
| 22 | + - `catalog_category_prepare_save` |
| 23 | + - `catalog_category_save_after` |
| 24 | + - `catalog_category_save_before` |
| 25 | + - `catalog_category_move_after` |
| 26 | + |
| 27 | + |
| 28 | +## DB collection flag `disable_staging_preview` |
| 29 | + |
| 30 | +- connection->select() |
| 31 | +- \Magento\Framework\DB\Adapter\Pdo\Mysql::select |
| 32 | +- Magento\Framework\DB\SelectFactory::create |
| 33 | +- Magento\Framework\DB\Select\SelectRenderer |
| 34 | +- // work with select... |
| 35 | +- \Magento\Framework\DB\Select::assemble |
| 36 | +- \Magento\Framework\DB\Select\SelectRenderer::render |
| 37 | + - \Magento\Framework\DB\Select\FromRenderer |
| 38 | + - \Magento\Staging\Model\Select\FromRenderer |
| 39 | + |
| 40 | + |
| 41 | +``` |
| 42 | +from = [ |
| 43 | + flag = [ |
| 44 | + joinType = 'from' |
| 45 | + tableName = 'flag' |
| 46 | + ] |
| 47 | +] |
| 48 | +``` |
| 49 | + |
| 50 | +``` |
| 51 | +$select->where($alias . '.created_in <= ?', $versionId); |
| 52 | +$select->where($alias . '.updated_in > ?', $versionId); |
| 53 | +``` |
| 54 | + |
| 55 | +Only known staged tables are affected - \Magento\Staging\Model\StagingList::getEntitiesTables - `*_entity` |
| 56 | + |
| 57 | + |
| 58 | +## Staging modification to the Magento database operations (row_id, entity managers) |
| 59 | + |
| 60 | +Flow modifications introduced by staging (triggers, row_id, data versions). |
| 61 | + |
| 62 | +- deletes foreign keys to removed entity_id column |
| 63 | +- creates foreign key to row_id |
| 64 | +- replaces indexes |
| 65 | + |
| 66 | +**Triggers** created differently! Attributes no longer have entity_id for `_cl` tables, so one more |
| 67 | +extra call to load entity_id by row_id is added to each update by schedule trigger. |
| 68 | + |
| 69 | +\Magento\Framework\Mview\View\Subscription::buildStatement |
| 70 | +vs |
| 71 | +\Magento\CatalogStaging\Model\Mview\View\Attribute\Subscription::buildStatement |
| 72 | + |
| 73 | + |
| 74 | +trg_catalog_product_entity_int_after_insert: |
| 75 | +```SQL |
| 76 | +-- before |
| 77 | +INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) VALUES (NEW.`entity_id`); |
| 78 | +-- after |
| 79 | +SET @entity_id = (SELECT `entity_id` FROM `catalog_product_entity` WHERE `row_id` = NEW.`row_id`); |
| 80 | +INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) values(@entity_id); |
| 81 | +``` |
| 82 | + |
| 83 | +trg_catalog_product_entity_int_after_update: |
| 84 | +```SQL |
| 85 | +-- before |
| 86 | +IF (NEW.`value_id` <=> OLD.`value_id` OR NEW.`attribute_id` <=> OLD.`attribute_id` OR NEW.`store_id` <=> OLD.`store_id` OR NEW.`entity_id` <=> OLD.`entity_id` OR NEW.`value` <=> OLD.`value`) THEN INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) VALUES (NEW.`entity_id`); END IF; |
| 87 | +-- after |
| 88 | +SET @entity_id = (SELECT `entity_id` FROM `catalog_product_entity` WHERE `row_id` = NEW.`row_id`); |
| 89 | +IF (NOT(NEW.`value_id` <=> OLD.`value_id`) OR NOT(NEW.`attribute_id` <=> OLD.`attribute_id`) OR NOT(NEW.`store_id` <=> OLD.`store_id`) OR NOT(NEW.`value` <=> OLD.`value`) OR NOT(NEW.`row_id` <=> OLD.`row_id`)) THEN INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) values(@entity_id); END IF; |
| 90 | +``` |
| 91 | + |
| 92 | +trg_catalog_product_entity_int_after_delete: |
| 93 | +```SQL |
| 94 | +-- before |
| 95 | +INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) VALUES (OLD.`entity_id`); |
| 96 | +-- after |
| 97 | +SET @entity_id = (SELECT `entity_id` FROM `catalog_product_entity` WHERE `row_id` = OLD.`row_id`); |
| 98 | +INSERT IGNORE INTO `catalog_product_flat_cl` (`entity_id`) values(@entity_id); |
| 99 | +``` |
| 100 | + |
| 101 | + |
| 102 | +## sequence tables |
| 103 | + |
| 104 | +Staging migration scripts do this: `row_id` becomes auto_increment, and `entity_id` (`page_id` etc.) column |
| 105 | +is no longer auto incremented, but still required! |
| 106 | + |
| 107 | +Requests like `INSERT INTO cms_page (identified) VALUES("test_page")` would result in error because page_id |
| 108 | +is normally auto_increment and not passed in request. |
| 109 | + |
| 110 | +Magento fixes this by manually generating auto increments for entity_id in separate sequence tables. |
| 111 | + |
| 112 | +Exmple: |
| 113 | + |
| 114 | +- \Magento\Framework\Model\ResourceModel\Db\CreateEntityRow::prepareData |
| 115 | +- `$output[$metadata->getIdentifierField()] = $metadata->generateIdentifier();` |
| 116 | +- \Magento\Framework\EntityManager\Sequence\Sequence::getNextValue |
| 117 | +- insert and return next ID into `sequence_*` table |
| 118 | + |
| 119 | +`sequence_cms_page`, `sequence_catalog_category`, `sequence_catalogrule`, ... |
| 120 | + |
| 121 | +In community edition (no staging), `$metadata->generateIdentifier()` because sequenceTable is not passed. |
| 122 | + |
| 123 | + |
| 124 | +- \Magento\Framework\EntityManager\MetadataPool::getMetadata |
| 125 | +- \Magento\Framework\EntityManager\MetadataPool::createMetadata |
| 126 | +- new \Magento\Framework\EntityManager\EntityMetadata |
| 127 | + - `sequence` argument: \Magento\Framework\EntityManager\Sequence\SequenceFactory::create: |
| 128 | + - no staging - sequence = null |
| 129 | + - staging defines `sequenceTable` - `new \Magento\Framework\EntityManager\Sequence\Sequence(connection, sequenceTable)` |
| 130 | + |
| 131 | + |
| 132 | + |
| 133 | +## Common staging plugins |
| 134 | + |
| 135 | +- FrontControllerInterface::beforeDispatch - validate `___version`, `___timestamp`, `___signature` |
| 136 | +- Magento\PageCache\Model\Config::afterIsEnabled - disabled in preview mode |
| 137 | +- Magento\Store\Model\BaseUrlChecker - disabled in preview mode |
| 138 | +- Magento\Framework\Stdlib\DateTime\Timezone::isScopeDateInInterval - interval never validated in preview mode |
| 139 | +- Magento\Store\Model\StoreResolver::getCurrentStoreId - use `___store` in preview mode |
| 140 | +- Magento\Customer\Model\Session::regenerateId, destroy - never regenerate/destroy in preview mode |
| 141 | +- getUrl - adds `___version`, `___store`, possibly `__timestamp`, `__signature` in preview mode |
| 142 | +- disables block_html caching |
| 143 | + |
| 144 | + |
| 145 | +## Staging-related modifications of the indexing process |
| 146 | + |
| 147 | +- plugin before Magento\Catalog\Controller\Category\View |
| 148 | +- \Magento\CatalogStaging\Model\Plugin\Controller\View::beforeExecute |
| 149 | +- \Magento\CatalogStaging\Model\Indexer\Category\Product\Preview::execute |
| 150 | +- \Magento\Catalog\Model\Indexer\Category\Product\AbstractAction::reindex |
| 151 | + |
| 152 | +Reindex using temporary tables with suffix `_catalog_staging_tmp`, |
| 153 | +e.g. `catalog_category_product_index_store1_catalog_staging_tmp` |
| 154 | + |
| 155 | + |
| 156 | +## Save staging update |
| 157 | + |
| 158 | +\Magento\SalesRuleStaging\Controller\Adminhtml\Update\Save::execute |
| 159 | +\Magento\Staging\Model\Entity\Update\Save::execute |
| 160 | + |
| 161 | +``` |
| 162 | +staging[mode]: save |
| 163 | +staging[update_id]: |
| 164 | +staging[name]: 3 September |
| 165 | +staging[description]: Everything is new |
| 166 | +staging[start_time]: 2020-09-03T07:00:00.000Z |
| 167 | +staging[end_time]: |
| 168 | +``` |
| 169 | + |
| 170 | +\Magento\Staging\Model\Entity\Update\Action\Pool::getAction( |
| 171 | + entityType = 'Magento\SalesRule\Api\Data\RuleInterface', |
| 172 | + namespace = 'save', |
| 173 | + actionType = mode = 'save' |
| 174 | +) |
| 175 | + |
| 176 | +- \Magento\Staging\Model\Entity\Update\Action\Save\SaveAction: |
| 177 | + - \Magento\Staging\Model\Entity\Update\Action\Save\SaveAction::createUpdate |
| 178 | + - \Magento\Staging\Model\EntityStaging::schedule |
| 179 | + - \Magento\CatalogRuleStaging\Model\CatalogRuleStaging::schedule |
| 180 | + - validates intersecting updates |
| 181 | + - sets `created_in`, saves entity model (catalog rule) |
| 182 | +- Magento\CatalogRuleStaging\Model\Rule\Hydrator |
| 183 | +- \Magento\CatalogRuleStaging\Model\Rule\Retriever::getEntity - $this->ruleRepository->get($entityId) |
| 184 | + |
| 185 | + |
| 186 | +## Preview |
| 187 | + |
| 188 | +All links are automatially updated: |
| 189 | +- \Magento\Framework\Url::getUrl |
| 190 | +- \Magento\Framework\Url\RouteParamsPreprocessorComposite::execute |
| 191 | +- \Magento\Staging\Model\Preview\RouteParamsPreprocessor::execute |
| 192 | +- `[_query][___version]=`, `[_query][___store]=` |
| 193 | +- `__timestamp=`, `__signature=` |
| 194 | + |
| 195 | +Example: |
| 196 | + |
| 197 | +vendor/magento/module-catalog/view/frontend/layout/default.xml: |
| 198 | +```XML |
| 199 | +<block class="Magento\Catalog\Block\FrontendStorageManager" name="frontend-storage-manager" before="-" |
| 200 | + template="Magento_Catalog::frontend_storage_manager.phtml"> |
| 201 | + <arguments> |
| 202 | + <!-- ... --> |
| 203 | + <item name="updateRequestConfig" xsi:type="array"> |
| 204 | + <item name="url" xsi:type="serviceUrl" path="/products-render-info"/> |
| 205 | + </item> |
| 206 | +``` |
| 207 | + |
| 208 | +- \Magento\Store\Model\Argument\Interpreter\ServiceUrl::evaluate |
| 209 | +- https://m24ee.local/rest/default/V1/?___store=default&___version=1599422820&__signature=b5cd66d9ea383ea57356f28cf2dd0f8edeecbc516b4d7982f8aef2ce4bcd4d32&__timestamp=1599165421products-render-info |
| 210 | + |
| 211 | + |
| 212 | +URL examples: |
| 213 | + |
| 214 | +- REST - `?___version=` |
| 215 | +- category - `?update_id=` |
| 216 | + |
| 217 | + |
| 218 | +Preview from admin panel: |
| 219 | + |
| 220 | +- https://m24ee.local/admin/staging/update/preview/key/.../?preview_store=default&preview_url=https:/m24ee.local/&preview_version=1599116400 |
| 221 | + - header and iframe |
| 222 | +- https://m24ee.local/?___version=1599116400&___store=default&__timestamp=1599077245&__signature=a8f3a05adb92374873a3f9c4e1d36c1bddcc9748b32b046af4264b496243f95d |
| 223 | + |
| 224 | + |
| 225 | +\Magento\Staging\Plugin\Framework\App\FrontController::beforeDispatch - validate signature: |
| 226 | + |
| 227 | +``` |
| 228 | +__signature = hash(sha256, "__version,__timestamp", secret key from env.php) |
| 229 | +``` |
| 230 | + |
| 231 | + |
| 232 | +## Set current version |
| 233 | + |
| 234 | +\Magento\CatalogStaging\Model\Category\DataProvider::getCurrentCategory |
| 235 | + |
| 236 | +``` |
| 237 | +$updateId = (int) $this->request->getParam('update_id'); |
| 238 | +$this->versionManager->setCurrentVersionId($update->getId()); |
| 239 | +``` |
| 240 | + |
| 241 | + |
| 242 | +## checkout-staging |
| 243 | + |
| 244 | +- disable place order in preview |
| 245 | +- any quote, saved in preview mode, is saved in db table `quote_preview` and deleted by cron |
0 commit comments