|
| 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. Such changes cause existing content to appear incorrectly, or not at all. |
| 4 | + |
| 5 | +To fix this limitation for versions 1.3+, Page Builder uses Magento's native upgrade mechanism, coupled with our content upgrade helpers. These helpers convert existing content so that 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 helpers, 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 | +To fix this issue, we used the Page Builder DOM helper classes (`Magento\PageBuilder\Model\Dom\*`) to create a converter and a data patch for the native Row content type: |
| 12 | + |
| 13 | +1. **Converter** (See `FixFullWidthRowPadding.php`) |
| 14 | +2. **Data Patch** (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 uses Page Builder's DOM helper classes to change the DOM of the Row content type within each master format it receives. |
| 21 | + |
| 22 | +Page Builder's `FixFullWidthRowPadding` converter 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 | +### Data patch class example |
| 86 | + |
| 87 | +The data patch class implements the `DataPatchInterface`. Specifically, it uses the Page Builder `UpgradeContentHelper` 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 exists. 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 Page Builder's content upgrade helpers for your own 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` to create a converter for your content type, using Page Builder's `FixFullWidthRowPadding` class as an example. |
| 227 | + |
| 228 | +1. Implement the `DataPatchInterface` to create a data patch 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, restore your database content to the original values, tweak your converter, then run `bin/magento setup:upgrade` again. Repeat as necessary. |
| 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. If necessary, you will need to create a converter and data patch to customize how the native content types are updated for your changes. |
| 243 | + |
| 244 | +For example, in version 1.3, we updated the configuration of the native Row content type. As mentioned, we moved the padding attribute of the `full-width` appearance from the `<main>` element to the `<inner>` element. So if your Row configuration is different in your custom content type (for example, you removed the `<inner>` element), then you will need to upgrade your overloaded Row as described in the previous steps. |
0 commit comments