|
| 1 | +# Customer Segments |
| 2 | + |
| 3 | +Conditions: |
| 4 | + |
| 5 | +- customer address attributes |
| 6 | +- customer attributes |
| 7 | +- cart item, total qty, totals |
| 8 | +- products in cart, in wishlist, viewed products, ordered products, viewed date, ordered date |
| 9 | +- order address, totals, number of orders, average total amount, order date, order status - only logged in |
| 10 | + |
| 11 | +Listens all model evetns, matches conditions by \Magento\CustomerSegment\Model\Segment\Condition\Order\Address::getMatchedEvents |
| 12 | + |
| 13 | + |
| 14 | +## Database |
| 15 | + |
| 16 | +- magento_customersegment_segment - name, conditions, actions |
| 17 | +- magento_customersegment_website - many-to-many |
| 18 | +- magento_customersegment_customer - website, added/updated date |
| 19 | +- magento_customersegment_event - after saving segment rule, holds events that related to selected conditions |
| 20 | + |
| 21 | + |
| 22 | +## Admin conditions |
| 23 | + |
| 24 | +namespace Magento\CustomerSegment; |
| 25 | + |
| 26 | +Admin form - old widget style tabs: |
| 27 | + |
| 28 | +- Block\Adminhtml\Customersegment\Edit\Tab\General |
| 29 | +- Block\Adminhtml\Customersegment\Edit\Tab\Conditions |
| 30 | + |
| 31 | +Rendering conditions form: |
| 32 | + |
| 33 | +render conditions -> renderer \Magento\Rule\Block\Conditions -> segment.getConditions -> root Model\Segment\Condition\Combine\Root |
| 34 | + |
| 35 | + |
| 36 | +Condition classes - hold matching rules: |
| 37 | + |
| 38 | +- Model\Condition\Combine\AbstractCombine |
| 39 | +- Model\Segment\Condition\Order\Address - order_address join sales_order join customer_entity |
| 40 | +- Model\Segment\Condition\Sales\Combine |
| 41 | + - Model\Segment\Condition\Sales\Ordersnumber - COUNT(*) |
| 42 | + - Model\Segment\Condition\Sales\Salesamount - sum/avg sales_order.base_grand_total |
| 43 | + - Model\Segment\Condition\Sales\Purchasedquantity - sum/avg sales_order.total_qty_ordered |
| 44 | +- Model\Segment\Condition\Customer\Address |
| 45 | + - Model\Segment\Condition\Customer\Address\DefaultAddress |
| 46 | + |
| 47 | + |
| 48 | +## Refresh Segment Data |
| 49 | + |
| 50 | +Some conditions don't work for guest visitors, despite description! Example - cart grand total amount: |
| 51 | + |
| 52 | +- click "Refresh" inside segment admin |
| 53 | +- Controller\Adminhtml\Report\Customer\Customersegment\Refresh::execute |
| 54 | +- Model\Segment::matchCustomers |
| 55 | +- Model\ResourceModel\Segment::aggregateMatchedCustomers |
| 56 | +- Model\ResourceModel\Segment::processConditions |
| 57 | +- Model\Segment\Condition\Combine\Root::getSatisfiedIds |
| 58 | +- // individual conditions |
| 59 | +- Model\Segment\Condition\Shoppingcart\Amount::getSatisfiedIds |
| 60 | + |
| 61 | +```SQL |
| 62 | +SELECT `quote`.`customer_id` FROM `quote` |
| 63 | +WHERE (quote.is_active=1) AND (quote.store_id IN('1')) AND (quote.base_grand_total >= '100') AND (customer_id IS NOT NULL) |
| 64 | +``` |
| 65 | + |
| 66 | + |
| 67 | +## Viewed product index |
| 68 | + |
| 69 | +Condition by recently viewed events **SEEMS BROKEN** when full page cache is enabled. |
| 70 | +This condition relies on `report_viewed_product_index` table, which is handled by report module. |
| 71 | + |
| 72 | +Configuration: |
| 73 | + |
| 74 | +- `reports/options/enabled` |
| 75 | +- `reports/options/product_view_enabled` |
| 76 | + |
| 77 | +\Magento\Reports\Model\ReportStatus::isReportEnabled |
| 78 | + |
| 79 | +\Magento\Reports\Model\Product\Index\Viewed |
| 80 | + |
| 81 | +`catalog_controller_product_view` -> \Magento\Reports\Observer\CatalogProductViewObserver |
| 82 | +- save into `report_viewed_product_index` |
| 83 | +- save into `report_event`, take customer from session |
| 84 | + |
| 85 | +This does not work when full page cache is enabled and Varnish returns HTML directly. |
| 86 | + |
| 87 | + |
| 88 | +## Listening to events and updating matched customers |
| 89 | + |
| 90 | +Segments needs to listen to some events related to conditions. After segment with conditions is saved, |
| 91 | +table `magento_customersegment_event` holds events that it listens to. |
| 92 | + |
| 93 | +Segment rule table holds rendered `condition_sql` column with placeholders: |
| 94 | +- :customer_id |
| 95 | +- :website_id |
| 96 | +- :quote_id |
| 97 | +- :visitor_id |
| 98 | + |
| 99 | +Updating customer segments on events: |
| 100 | + |
| 101 | +- event `customer_login` |
| 102 | +- Observer\ProcessEventObserver::execute |
| 103 | +- Model\Customer::processEvent |
| 104 | +- Model\Customer::getActiveSegmentsForEvent |
| 105 | +- Model\ResourceModel\Segment\Collection::addEventFilter - join table `magento_customersegment_event` |
| 106 | +- Model\Customer::_processSegmentsValidation |
| 107 | +- Model\Segment::validateCustomer |
| 108 | + * visitor_id, quote_id passed for guest |
| 109 | +- Model\Segment\Condition\Combine\Root::isSatisfiedBy |
| 110 | + * all conditions[].isSatisfiedBy |
| 111 | + * all conditions[].Model\Condition\Combine\AbstractCombine::getConditionsSql |
| 112 | + |
| 113 | + |
| 114 | +Result: |
| 115 | + |
| 116 | +- Matched segments are added to `magento_customersegment_customer` |
| 117 | +- http context \Magento\CustomerSegment\Helper\Data::CONTEXT_SEGMENT = 'customer_segment' is updated with new segments |
| 118 | +- customerSession.setCustomerSegmentIds |
| 119 | + |
| 120 | + |
| 121 | +Event -> interested segment rules. |
| 122 | + |
| 123 | + |
| 124 | +## Impact on performance |
| 125 | + |
| 126 | +1. Customer segments are added to full page cache keys |
| 127 | + |
| 128 | + \Magento\CustomerSegment\Helper\Data::CONTEXT_SEGMENT = 'customer_segment' |
| 129 | + |
| 130 | + \Magento\PageCache\Model\App\Response\HttpPlugin::beforeSendResponse |
| 131 | + \Magento\Framework\App\Response\Http::sendVary |
| 132 | + \Magento\Framework\App\Http\Context::getVaryString |
| 133 | + |
| 134 | + Example: |
| 135 | + |
| 136 | + ```php |
| 137 | + sha1($this->serializer->serialize([ |
| 138 | + 'customer_group' => '1', |
| 139 | + 'customer_logged_in' => true, |
| 140 | + 'customer_segment' => ["1"] |
| 141 | + ])) |
| 142 | + ``` |
| 143 | + |
| 144 | + This lowers chances of hitting cached page, increasing load on the server. |
| 145 | + |
| 146 | +1. Listens to many object save events, calculates matching conditions |
| 147 | + |
| 148 | + |
| 149 | +## DepersonalizePlugin |
| 150 | + |
| 151 | +Does 2 things: |
| 152 | + |
| 153 | +1. Makes sure customerSession.CustomerSegmentIds is not deleted by Customer module DepersonalizePlugin |
| 154 | +1. Sets full page cache key httpContext->setValue(Data::CONTEXT_SEGMENT) |
| 155 | + |
| 156 | + |
| 157 | +\Magento\Framework\Pricing\Render\Layout::loadLayout: |
| 158 | +``` |
| 159 | + $this->layout->getUpdate()->load(); |
| 160 | + // 1. \Magento\Customer\Model\Layout\DepersonalizePlugin::beforeGenerateXml: |
| 161 | + // - remember customerGroupId and formKey from session |
| 162 | + // 2. \Magento\CustomerSegment\Model\Layout\DepersonalizePlugin::beforeGenerateXml: |
| 163 | + // - remember customerSegmentIds from customer session |
| 164 | + $this->layout->generateXml(); |
| 165 | + $this->layout->generateElements(); |
| 166 | + // 3. \Magento\PageCache\Model\Layout\DepersonalizePlugin::afterGenerateElements: |
| 167 | + // - event `depersonalize_clear_session` |
| 168 | + // - session_write_close(); |
| 169 | + // - clear message session |
| 170 | + // 4. \Magento\Customer\Model\Layout\DepersonalizePlugin::afterGenerateElements: |
| 171 | + // - clear customer session |
| 172 | + // - restore session._form_key |
| 173 | + // - restore customer session.customerGroupId, empty customer object with customer group |
| 174 | + // 5. \Magento\Catalog\Model\Layout\DepersonalizePlugin::afterGenerateElements: |
| 175 | + // - clear catalog session |
| 176 | + // 6. \Magento\Persistent\Model\Layout\DepersonalizePlugin::afterGenerateElements: |
| 177 | + // - clear persistent session |
| 178 | + // 7. \Magento\CustomerSegment\Model\Layout\DepersonalizePlugin::afterGenerateElements: |
| 179 | + // - httpContext->setValue(Data::CONTEXT_SEGMENT) |
| 180 | + // - restore customerSession->setCustomerSegmentIds |
| 181 | + // 8. \Magento\Checkout\Model\Layout\DepersonalizePlugin::afterGenerateElements: |
| 182 | + // - clear checkout session |
| 183 | +``` |
| 184 | +
|
| 185 | +## All Depersonalize examples |
| 186 | +
|
| 187 | +\Magento\Catalog\Model\Layout\DepersonalizePlugin::afterGenerateElements: |
| 188 | +- clear catalog session |
| 189 | +
|
| 190 | +
|
| 191 | +
|
| 192 | +## Catalog frontend action |
| 193 | +
|
| 194 | +Magento has alternative implementation of recently viewed products, handled by Catalog module. |
| 195 | +It works with full page cache, but: |
| 196 | +
|
| 197 | +- is **not integrated** into customer segment conditions |
| 198 | +- frontend synchronization must be enabled, adding extra calls on every page |
| 199 | +
|
| 200 | +When sync is enabled, it makes 2 AJAX calls with every page opening: |
| 201 | +- synchronize recently viewed products from local storage |
| 202 | +- synchronize recently compared products from local storage |
| 203 | +
|
| 204 | +Configuration: |
| 205 | +
|
| 206 | +- `catalog/recently_products/synchronize_with_backend` = 0 |
| 207 | +- `catalog/recently_products/recently_viewed_lifetime` = 1000 |
| 208 | +- `catalog/recently_products/scope` - store view, store, website |
| 209 | +
|
| 210 | +Other: |
| 211 | +
|
| 212 | +- db table `catalog_product_frontend_action` |
| 213 | +- cron `catalog_product_frontend_actions_flush` - every minute \Magento\Catalog\Cron\FrontendActionsFlush: |
| 214 | +- deletes compared, viewed older than (default 1000) seconds |
| 215 | +- frontend controller 'catalog/product_frontend_action/synchronize' |
| 216 | +
|
| 217 | +Notable classes: |
| 218 | +
|
| 219 | +- Magento\Catalog\Model\FrontendStorageConfigurationPool |
| 220 | +- Magento\Catalog\Model\Widget\RecentlyViewedStorageConfiguration |
| 221 | +
|
| 222 | +
|
| 223 | +\Magento\Catalog\Block\FrontendStorageManager: |
| 224 | +
|
| 225 | +- recently_viewed_product |
| 226 | +- recently_compared_product |
| 227 | +- product_data_storage |
| 228 | +- vendor/magento/module-catalog/view/frontend/web/js/storage-manager.js |
| 229 | +
|
| 230 | +customer data section: |
| 231 | +
|
| 232 | +- recently_viewed_product |
| 233 | +- recently_compared_product |
| 234 | +- product_data_storage |
| 235 | +
|
| 236 | +Triggering recently viewed update on frontend: |
| 237 | +
|
| 238 | +- catalog_product_view.xml |
| 239 | +- Magento\Catalog\Block\Ui\ProductViewCounter |
| 240 | +- vendor/magento/module-catalog/view/frontend/templates/product/view/counter.phtml |
| 241 | +- vendor/magento/module-catalog/view/frontend/web/js/product/view/provider.js |
| 242 | +
|
| 243 | +\Magento\Catalog\Model\ProductRender - DTO |
| 244 | +
|
| 245 | +\Magento\Catalog\Ui\DataProvider\Product\ProductRenderCollectorComposite::collect: |
| 246 | +
|
| 247 | +- Url - url, addToCartButton, addToCompareButton |
| 248 | +- AdditionalInfo - isSalable, type, name, id |
| 249 | +- Image - images |
| 250 | +- Price - priceInfo |
| 251 | +- ... wishlist, review, tax, gift card, bundle price |
| 252 | +
|
| 253 | +
|
| 254 | +widget `catalog_recently_compared` template `product/widget/compared/list.phtml`: |
| 255 | +
|
| 256 | +- UI component `widget_recently_compared` - listing, columns |
| 257 | +- Magento_Catalog/js/product/provider-compared.js |
| 258 | +- Magento_Catalog/js/product/storage/storage-service.js |
| 259 | +- Magento_Catalog/js/product/storage/data-storage.js - get product data from customer section. recently viewed, compared |
0 commit comments