|
| 1 | +# How to make configuration changes backwards compatible |
| 2 | + |
| 3 | +Before version 1.3, changes to a content-type configuration could (and usually would) break the existing content that was saved with the previous configuration. Why? Because a content type's configuration maps data from its source (the master format) to its display templates. So when the configuration mapping changes, the display of existing content might also change. With significant configuration changes, data (such as styles, attributes, and html) is lost. This change causes existing content to appear incorrectly, or not at all. |
| 4 | + |
| 5 | +The Page Builder content upgrade framework provides an API to convert existing content so it maps to new configurations and displays correctly. |
| 6 | + |
| 7 | +## Example usage for Row |
| 8 | + |
| 9 | +The Page Builder team recently had to change the configuration of the Row's full-width appearance to fix a layout issue. The fix was simple. We moved a style attribute from one element in the Row's full-width appearance to another element. But without the upgrade framework, our change to the Row's configuration would have broken all previously saved Page Builder content with Rows. And because all Page Builder content starts with a Row, all Page Builder content would be broken! |
| 10 | + |
| 11 | +Our fix to this issue was to build a framework that requires two classes: |
| 12 | + |
| 13 | +1. **Converter** (See `FixFullWidthRowPadding.php`) |
| 14 | +2. **Patcher** (See `UpgradeFullWidthPadding.php`) |
| 15 | + |
| 16 | +  |
| 17 | + |
| 18 | +### Converter class example |
| 19 | + |
| 20 | +The converter class implements the `DataConverterInterface`. Specifically, it implements the `convert` function where it makes the actual DOM changes to the content types within each master format it receives. |
| 21 | + |
| 22 | +Page Builder's `FixFullWidthRowPadding` class is provided here as an example implementation: |
| 23 | + |
| 24 | +```php |
| 25 | +<?php |
| 26 | +/** |
| 27 | + * Copyright © Magento, Inc. All rights reserved. |
| 28 | + * See COPYING.txt for license details. |
| 29 | + */ |
| 30 | + |
| 31 | +declare(strict_types=1); |
| 32 | + |
| 33 | +namespace Magento\PageBuilder\Setup\Converters; |
| 34 | + |
| 35 | +use Magento\Framework\DB\DataConverter\DataConverterInterface; |
| 36 | +use Magento\PageBuilder\Model\Dom\Adapter\ElementInterface; |
| 37 | +use Magento\PageBuilder\Model\Dom\HtmlDocument; |
| 38 | +use Magento\PageBuilder\Model\Dom\HtmlDocumentFactory; |
| 39 | + |
| 40 | +/** |
| 41 | + * Converter to move padding in full width columns from the main row element to the inner element |
| 42 | + */ |
| 43 | +class FixFullWidthRowPadding implements DataConverterInterface |
| 44 | +{ |
| 45 | + /** |
| 46 | + * @var HtmlDocumentFactory |
| 47 | + */ |
| 48 | + private $htmlDocumentFactory; |
| 49 | + |
| 50 | + /** |
| 51 | + * @param HtmlDocumentFactory $htmlDocumentFactory |
| 52 | + */ |
| 53 | + public function __construct(HtmlDocumentFactory $htmlDocumentFactory) |
| 54 | + { |
| 55 | + $this->htmlDocumentFactory = $htmlDocumentFactory; |
| 56 | + } |
| 57 | + |
| 58 | + /** |
| 59 | + * @inheritDoc |
| 60 | + */ |
| 61 | + public function convert($value) |
| 62 | + { |
| 63 | + /** @var HtmlDocument $document */ |
| 64 | + $document = $this->htmlDocumentFactory->create([ 'document' => $value ]); |
| 65 | + $fullWidthRows = $document->querySelectorAll("div[data-content-type='row'][data-appearance='full-width']"); |
| 66 | + /** @var ElementInterface $row */ |
| 67 | + foreach ($fullWidthRows as $row) { |
| 68 | + $style = $row->getAttribute("style"); |
| 69 | + preg_match("/padding:(.*?);/", $style, $results); |
| 70 | + $padding = isset($results[1]) ? trim($results[1]) : ''; |
| 71 | + if (!$padding) { |
| 72 | + continue; |
| 73 | + } |
| 74 | + // remove padding from main row element |
| 75 | + $row->removeStyle("padding"); |
| 76 | + // add padding to inner row element |
| 77 | + $innerDiv = $row->querySelector(".row-full-width-inner"); |
| 78 | + $innerDiv->addStyle("padding", $padding); |
| 79 | + } |
| 80 | + return $fullWidthRows->count() > 0 ? $document->stripHtmlWrapperTags() : $value; |
| 81 | + } |
| 82 | +} |
| 83 | +``` |
| 84 | + |
| 85 | +### Patcher class example |
| 86 | + |
| 87 | +The patcher class implements the `DataPatchInterface`. Specifically, it uses the framework's `UpgradeContentHelper.php` class to apply the converter class to all the database entities where Page Builder content exists. These locations are provided by the `UpgradableEntitiesPool`, described later in this topic |
| 88 | + |
| 89 | +Page Builder's `UpgradeFullWidthPadding` class is provided here as an example implementation: |
| 90 | + |
| 91 | +```php |
| 92 | +<?php |
| 93 | +/** |
| 94 | + * Copyright © Magento, Inc. All rights reserved. |
| 95 | + * See COPYING.txt for license details. |
| 96 | + */ |
| 97 | +namespace Magento\PageBuilder\Setup\Patch\Data; |
| 98 | + |
| 99 | +use Magento\Framework\DB\FieldDataConversionException; |
| 100 | +use Magento\Framework\Setup\Patch\DataPatchInterface; |
| 101 | +use Magento\PageBuilder\Setup\Converters\FixFullWidthRowPadding; |
| 102 | +use Magento\PageBuilder\Setup\UpgradeContentHelper; |
| 103 | + |
| 104 | +/** |
| 105 | + * Patch upgrade mechanism allows us to do atomic data changes |
| 106 | + */ |
| 107 | +class UpgradeFullWidthPadding implements DataPatchInterface |
| 108 | +{ |
| 109 | + /** |
| 110 | + * @var UpgradeContentHelper |
| 111 | + */ |
| 112 | + private $helper; |
| 113 | + |
| 114 | + /** |
| 115 | + * @param UpgradeContentHelper $helper |
| 116 | + */ |
| 117 | + public function __construct( |
| 118 | + UpgradeContentHelper $helper |
| 119 | + ) { |
| 120 | + $this->helper = $helper; |
| 121 | + } |
| 122 | + |
| 123 | + /** |
| 124 | + * Do upgrade |
| 125 | + * |
| 126 | + * @return void |
| 127 | + * @throws FieldDataConversionException |
| 128 | + */ |
| 129 | + public function apply() |
| 130 | + { |
| 131 | + $this->helper->upgrade([ |
| 132 | + FixFullWidthRowPadding::class |
| 133 | + ]); |
| 134 | + } |
| 135 | + |
| 136 | + /** |
| 137 | + * @inheritdoc |
| 138 | + */ |
| 139 | + public function getAliases() |
| 140 | + { |
| 141 | + return []; |
| 142 | + } |
| 143 | + |
| 144 | + /** |
| 145 | + * @inheritdoc |
| 146 | + */ |
| 147 | + public static function getDependencies() |
| 148 | + { |
| 149 | + return []; |
| 150 | + } |
| 151 | +} |
| 152 | +``` |
| 153 | + |
| 154 | +## UpgradableEntitiesPool |
| 155 | + |
| 156 | +The `UpgradableEntitiesPool` provides the locations in the database where Page Builder content can exist. By default, these entities include: `cms_block`, `cms_page`, `catalog_category_entity_text`, `catalog_product_entity_text`, and `pagebuilder_template`. Page Builder defines these entities in `Magento/PageBuilder/etc/di.xml`, as shown here: |
| 157 | + |
| 158 | +```xml |
| 159 | +<type name="Magento\PageBuilder\Model\UpgradableEntitiesPool"> |
| 160 | + <arguments> |
| 161 | + <argument name="entities" xsi:type="array"> |
| 162 | + <item name="cms_block" xsi:type="array"> |
| 163 | + <item name="identifier" xsi:type="string">block_id</item> |
| 164 | + <item name="fields" xsi:type="array"> |
| 165 | + <item name="content" xsi:type="boolean">true</item> |
| 166 | + </item> |
| 167 | + </item> |
| 168 | + <item name="cms_page" xsi:type="array"> |
| 169 | + <item name="identifier" xsi:type="string">page_id</item> |
| 170 | + <item name="fields" xsi:type="array"> |
| 171 | + <item name="content" xsi:type="boolean">true</item> |
| 172 | + </item> |
| 173 | + </item> |
| 174 | + <item name="catalog_category_entity_text" xsi:type="array"> |
| 175 | + <item name="identifier" xsi:type="string">value_id</item> |
| 176 | + <item name="fields" xsi:type="array"> |
| 177 | + <item name="value" xsi:type="boolean">true</item> |
| 178 | + </item> |
| 179 | + </item> |
| 180 | + <item name="catalog_product_entity_text" xsi:type="array"> |
| 181 | + <item name="identifier" xsi:type="string">value_id</item> |
| 182 | + <item name="fields" xsi:type="array"> |
| 183 | + <item name="value" xsi:type="boolean">true</item> |
| 184 | + </item> |
| 185 | + </item> |
| 186 | + <item name="pagebuilder_template" xsi:type="array"> |
| 187 | + <item name="identifier" xsi:type="string">template_id</item> |
| 188 | + <item name="fields" xsi:type="array"> |
| 189 | + <item name="template" xsi:type="boolean">true</item> |
| 190 | + </item> |
| 191 | + </item> |
| 192 | + </argument> |
| 193 | + </arguments> |
| 194 | +</type> |
| 195 | +``` |
| 196 | + |
| 197 | +If you have created additional database entities for storing Page Builder content, you need to add your custom entity to your `etc/di.xml` as shown in the following example: |
| 198 | + |
| 199 | +```xml |
| 200 | +<type name="Magento\PageBuilder\Model\UpgradableEntitiesPool"> |
| 201 | + <arguments> |
| 202 | + <argument name="entities" xsi:type="array"> |
| 203 | + <item name="my_custom_block" xsi:type="array"> |
| 204 | + <item name="identifier" xsi:type="string">custom_block_id</item> |
| 205 | + <item name="fields" xsi:type="array"> |
| 206 | + <item name="content" xsi:type="boolean">true</item> |
| 207 | + </item> |
| 208 | + </item> |
| 209 | + </argument> |
| 210 | + </arguments> |
| 211 | +</type> |
| 212 | +``` |
| 213 | + |
| 214 | +## How to upgrade your custom content types |
| 215 | + |
| 216 | +To use this framework for your custom content-type configuration changes, follow these steps: |
| 217 | + |
| 218 | +1. Set up a new environment that uses a _copy_ of your production data, far away from any real data. |
| 219 | + |
| 220 | +1. Create a test branch off your local environment where you can make and test your data patch converters. You will switch back and forth off this branch to reset the data in your Magento instance as needed. |
| 221 | + |
| 222 | +1. Within the test branch for your content type module changes, set up a directory structure similar to this: |
| 223 | + |
| 224 | +  |
| 225 | + |
| 226 | +1. Implement the `DataConverterInterface` for your content type, using Page Builder's `FixFullWidthRowPadding` class as an example. |
| 227 | + |
| 228 | +1. Implement the `DataPatchInterface` for your content type, using Page Builder's `UpgradeFullWidthPadding` class as an example. |
| 229 | + |
| 230 | +1. Make a copy of the data in the `content` fields of existing entities (`cms_page`, `cms_block`, and so on). You will compare this content with the content changes made after running your upgrade patch. |
| 231 | + |
| 232 | +1. Run `bin/magento setup:upgrade` to test your custom conversion. |
| 233 | + |
| 234 | + After running `setup:upgrade`, you should see an entry at the bottom of the `patch_list` table of your Magento database. The entry uses your module's namespace with the name of your DataPatch class. So use a descriptive name to ensure the `patch_list` entry makes sense to others. |
| 235 | + |
| 236 | +1. Compare your post-upgraded content to the previous content. |
| 237 | + |
| 238 | + If the conversion didn't convert your content as planned, remove your entry from the `patch_list` table, switch off away from your test branch, and run `bin/magento setup:upgrade` to reset the database content to try again. |
| 239 | + |
| 240 | +## How to upgrade overloaded Page Builder content types |
| 241 | + |
| 242 | +If you have overloaded the configurations of native Page Builder content types, you need to review Page Builder's native configuration changes for each release and create converters to customize how the native content types are updated for your changes, as necessary. |
| 243 | + |
| 244 | +For example, In version 1.3, we updated the configuration of the native Row content type. Specifically, we moved the padding attribute of the `full-width` appearance from the `<main>` element to the `<inner>` element. So if the Row configuration is different in your custom content type, maybe you removed the `<inner>` element, then you will need to upgrade your overloaded Row as described in the previous steps. |
0 commit comments