Magento 2.x becoming more powerful and playing ambidextrous role while serving the needs for e-commerce applications.
This lockdown pushed me to take an arduous expedition to Magento 2.x landscape. After all sightseeing, I would like to share you the breadcrumbs for typical flow of Magento 2.x checkout. I wish, this may help you on your M2 questing.
The journey get kick-start with URL " http://localhost/symphisys/checkout".
Let's zoom-in the URL.
HTTP | Http protocol helps to have the communication between client (Browser) and server for request / response transactions. |
host | Pointes to mechine's unique address |
80 | Web server port |
symphisys | It's a context path to refer Magento project. |
checkout | Magento module name |
Magento project execution starts with the path "/symphisys/checkout". For a quick reference, below shared an elegant request sequence flow diagram, It may give you closer look on high level overview of complex Magento 2.x checkout flow.
- index.php
- bootstrap.php
- http.php
- front controller.php
- onepage.php
- page.php
- index.php onepage.php action.php
- base.php
- layout.php
- layout processor.php
- php.php
- template.php
- require.js
- onepage.phtml
- jsonhextag.php
- render.php
- jquery.js
- main.js
- scripts.js
- app.js
- loader.js
- engine.js
- knockout.js
- layout.js
- render.js
- i18.js
- shipping.html
- form.html
- input.html
- field.html
I have articulated below all primary involved components.
Index.PHP
- Web server invokes the Index.PHP file.
- Index.PHP is the entry point to load the application.
- The primary role is to initialize the Bootstrap class to handle request / response transaction.
- You can find the Index file under Magento project. In our case our Magento project name is ‘symphisys’.
Bootstrap.PHP
Bootstrap having significant role to execute below logics.
- Sets the reported errors and warnings. Errors will be displayed at front end, If only you uncomment the line -> #ini_set ('display errors', 1);
- Disables the ‘phar’ feature to avoid the archiving application.
- Validates the PHP version for Magento2.x. If it finds version mismatch, error shown to end user.
- Encapsulate the variables like [SYSTEM/ REMOTE/ CONTEXT/ HTTP / SERVER/ REDIRECT/ PHP/ PATH]. key - value pair set to $_SERVER object.
- Auto loads the Magento 2.x OOTB [Modules from vendor folder] and custom modules [Symphisys specific modules].
- Initializes the Object manager [OM] class.
- Load the entries from DI.xml across the modules and wrap it to the config object for future usage.
- Starts the Profiler class to trace the application performance.
- Initializes the error handler to catch the exceptions while program compilation.
- Checks the application maintenance status.
- Checks the Magento installation success status.
- Dispatch the request to HTPP class.
- Sets the default time zone to UTC.
Dependency Injection (DI)
- Dependency Injection, The most tossed word from Magento 2.x on internet.
- There are around 2000 Magento OOTB classes are registered in DI.xml for their object creation at runtime and there are more included to customize the class / method behavior. This may results, around 80% of the class steering happening through the DI.xml.
- It briefs how much Magento having the dependency on DI 😊
What is Dependency Injection [DI]?
Dependency Injection is more over like a design pattern and it’s about an organized play between the classes to serve the needs for each other and which helps to maintain the application performance symphony.
In simple words:
If ‘customer class’ has dependency on ‘Address class’ to get the address data: This is Dependency
If your instantiating the ‘Address class’ in ‘customer class’ at constructor / method / field level: This is Injection
All together it’s dependency injection or dependency – object – injection.
There are 2 types of injections
Constructor dependency Injection
Example :
public function __construct(\Magento\Quote\Api\CartRepositoryInterface $quoteRepository) { $this->quoteRepository = $quoteRepository;}
Automatic dependency Injection
Example :
$checkoutHelper = $this->_objectManager->get(\Magento\Checkout\Helper\Data::class);
DI.xml role?
DI.xml role here to add dynamics to dependency injection. With DI particularly you can steer the class / method behavior without changing its originality and below are other features,
- Class in-heritance.
- Passing method arguments.
- You can declare the proxy classes to load at constructor.
- Create plug-ins (Interceptors) to write pre / post method code.
Why should DI?
In-general, High-level applications like Magento, built-in with complex logic with creating hundreds of modules (Magento 2.x having around 400 hundred modules). It’s very difficult to maintain the customizations / enhancements without disturbing the existing code. So, this complexity anticipates the need of design patterns. Dependency injection is popular among the design patterns, with help of DI, you will get all below benefits.
- It helps to create an abstraction layer that de-couples the high-level and low-level modules from each other.
- New changes will not lead to revamp the modules.
- Easy control over the module customization.
- Avoids the duplicate coding.
- Reusability
- Performance
Object Manager [OM]
Object manager concept in Magento 2.x is much like a factory design pattern. OM’s responsibility is to create the instance for requested class.
It creates two types of instances,
- Global scope instance like a singleton: only one instance get created to serve the all requests.
- Request scope instance: for every request, new instance will get created.
HTTP.PHP
HTTP job is to choose the area code based on the ‘Front Name’ value.
- Magento project grouped with multiple modules and labeled with below list of area codes. Each Area code represents the target area of execution for the request processing.
- “Front Name” value available in request path info.
- HTTP class fetches the area-code based on the “Front Name” value. If the request comes through the web store [webroot: index.php] and “Front Name” value not matches to ‘rest’ or ‘soap’ or ‘front name resolver’ then It picks the ‘frontend’ as the area code as default. For checkout page “Front Name” value is ‘checkout’.
- Pass the request to Front Controller and gets the response.
Area Code | Target area of execution |
---|---|
frontend | storefront modules |
adminhtml | admin dashboard modules |
crontab | crontab modules |
webapi_rest | REST API code |
webapi_soap | SOAP API code |
FrontController.PHP
Front controller job is to pick the right router class for given request.
- It iterates the below routers list and pick the right router which matches with the data in request. For checkout landing page, it picks the Base router.
[robots = Magento\Robots\Controller\Router],
[urlrewrite = Magento\UrlRewrite\Controller\Router],
[standard = Magento\Framework\App\Router\Base],
[cms = Magento\Cms\Controller\Router],
[default = Magento\Framework\App\Router\DefaultRouter] - Passes the request to Base router class.
Base.PHP
Base router class job is to create the controller path and dispatch the request to controller class.
- It forms the controller path with filling the below parameters.
a. [ModuleFrontName =checkout] => ModuleFrontName value gets from the URL path.
b. [ActionPath =index] => ActionPath value gets from the Default Path component.
c. [ActionName =index] => ActionName value gets from the Default Path component. - Create an action instance for path ‘checkout/index/index’.
- Populate the instance with all necessary data. For reference mentioned below few of the values from action instance. From this point, compilation takes the momentum to prepare the response.
• request
• response
• objectmanager
• view
• accountmanagement
• customerrepository
• layoutfactory
• quoterepository
• resultjsonfactory
• resultlayoutfactory
• resultpagefactory
• resultrawfactory
• scopeConfig
• formKeyValidator
• translateInline
• coreRegistry
• accountManagement
• customerRepository
• customerSession
• context - Call to ‘dispatch’ method at onepage.php. It’s the super class for checkout index controller.
Index.PHP / onepage.PHP / Action.PHP
Index is an Action class which works as controller here in MVC. Below are the following operations.
- Checks the cart status with conditions like, empty cart / error cart / min-amount. If any condition matches, it redirects to cart page.
- Checks the user login status. Guest checkout or authorized checkout?
- Load the order and customer data to checkout and customer session objects.
- Initializes the Page object (Framework\View\Result\Page).
Page.PHP
Page class is the gateway to mills the checkout page data.
- Page invokes the builder class to tailor the master layout by appending all the dependency layouts from below modules.
• /module-catalog-search/view/frontend/layout/
• /module-checkout/view/frontend/page_layout/
• /theme-frontend-luma/Magento_Theme/layout/
• /module-checkout/view/frontend/layout/
• /module-catalog/view/frontend/layout/
• /module-search/view/frontend/layout/
• /module-bundle/view/base/layout/ - Layouts get loaded with the help of handles. Handle represents the layout xml file. Checkout master layout created with help of below handles.
• default
• default_head_blocks
• checkout_index_index
• checkout_cart_sidebar_total_renderers
• checkout_cart_sidebar_item_renderers
• checkout_cart_sidebar_item_price_renderers - Layout class traverse through the master layout nodes with help of Classes [Generatorpool / Readerpool / Blocks / Container / Header / uiElement ] to create a map. Map contains the key - value pair of layout nodes and blocks. For the reference, shared below the map data for checkout page. Below are the blocks behind the checkout page to prepare and fetch the data.
- Page having the other dependency classes like [Layout.php / php.php / template.php / Render.php/ JsonHexTag.php] to render the response.
• Layout.php -> read the layout xml file
• Renderer.php -> Render the page assets like, Meta tags/Page title/JS/CS/ Fevicon.
• Template.php -> fetch the phtml for the assigned block
• PHP.php -> Render the block response in phtml
• JsonHexTag.php -> response converted to JSON object - Load the onepage.phtml which is the template for the checkout landing page.
Layout Nodes | Associated Blocks |
---|---|
checkout.root | \Checkout\Block\Onepage |
head.additional | \Framework\View\Element\Template |
head.components | \Framework\View\Element\Js\Components |
require.js | \Framework\View\Element\Template |
requirejs-config | \RequireJs\Block\Html\Head\Config |
js_cookies | \Framework\View\Element\Js\Cookie |
global_notices | \Theme\Block\Html\Notices |
skip_to_content.target | \Framework\View\Element\Template |
logo | \Theme\Block\Html\Header\Logo |
page.main.title | \Theme\Block\Html\Title |
copyright | \Theme\Block\Html\Footer |
absolute_footer | \Theme\Block\Html\Footer |
formkey | \Framework\View\Element\FormKey |
authentication-popup | \Customer\Block\Account\AuthenticationPopup |
customer.section.config | \Customer\Block\SectionConfig |
customer.customer.data | \Customer\Block\CustomerData |
customer.data.invalidation.rules | \Customer\Block\CustomerScopeData |
product.price.render.default | \Framework\Pricing\Render |
frontend-storage-manager | \Catalog\Block\FrontendStorageManager |
sales_page_head_components | \Framework\View\Element\Js\Components |
checkout_page_head_components | \Framework\View\Element\Js\Components |
cookie_notices | \Cookie\Block\Html\Notices |
cookie_config | \Framework\View\Element\Js\Cookie |
google_analytics | \GoogleAnalytics\Block\Ga |
logger | \Ui\Block\Logger |
theme.active.editor | \Ui\Block\Wysiwyg\ActiveEditor |
google.verification | \Framework\View\Element\Template |
google.gtag.global | \Framework\View\Element\Template |
google.gtag.cart | \Framework\View\Element\Template |
pagecache_page_head_components | \Framework\View\Element\Js\Components |
pageCache | \PageCache\Block\Javascript |
bundle_checkout_page_head_components | \Framework\View\Element\Js\Components |
reports_page_head_components | \Framework\View\Element\Js\Components |
newsletter_head_components | \Framework\View\Element\Js\Components |
translate-config | \Translation\Block\Html\Head\Config |
wishlist_page_head_components | \Framework\View\Element\Js\Components |
amazon_config | \Amazon\Core\Block\Config |
emailCapture | \Framework\View\Element\Template |
ddg.checkout.tag | \Dotdigitalgroup\Email\Block\Tracking |
signifyd.fingerprint | \Signifyd\Block\Fingerprint |
Onepage.PHP
Onepage.PHPis a block and has a responsibility to fetch the data for checkout page’s body content. Onepage.phtml has triggers to invoke methods in onepage.PHP class.
- Onepage block render the layout-processors to get the data of,
• Shipping
• Billing
• Shipping method
• Country
• State
• Cart summary
• Tax
• Payment methods
• Authentication
• Progress-Bar - Below is the list of layout processors involved
- Response sent back to the onepage.phtml.
- Extracting JSON object will be taken care by the RequireJS / underscoreJS / Jquery / Knockout framework from onepage.phtml.
\Checkout\Block\Checkout\LayoutProcessor | Fetch the shipping / billing addresses details for the customer |
\Checkout\Block\Checkout\TotalsProcessor | Get the cart total details |
\Checkout\Block\Checkout\DirectoryDataProcessor | Get the country / state details |
\ReCaptcha\Block\LayoutProcessor\Checkout\Onepage | Get the captcha details |
\PaypalReCaptcha\Block\LayoutProcessor\Checkout\Onepage | Get the pay pal payment details |
\Shipping\Block\Checkout\LayoutProcessor | Get the shipping method details |
\Tax\Block\Checkout\SortTaxMessageProcessor | Get the tax details |
Onepage.PHTML
Onepage.phtml will do the following operations.
- Initiate the onepage.php block.
- Init the scope variable as ‘checkout’ at data-bind="scope:'checkout'" tag.
<div id="checkout" data-bind="scope:'checkout'" class="checkout-container" > </div> - "text/x-magento-init" is custom type java script attribute which is belongs to Magento 2.x.
- Above is the piece of code to trigger the getJsLayout() method in onepage.php block.
- It returns the response JSON object and assigned to ‘#checkout’ variable.
- ‘#checkout ‘can be referred as root level ‘view model’ object. It has the details related to the nodes and associated JS components.
It looks like below. - Window.checkoutConfig variable get loaded with customer / active cart / shipping freights data.
- Let’s have the closer look on, how the ‘checkout’ data get extracted through Requirejs / Jquery / KO / UnderscoreJS libraries in checkout shipping page.
<script type="text/x-magento-init"> { "#checkout": { "Magento_Ui/js/core/app": <?= / @noEscape / $block->getJsLayout() ?> } }
JS libraries used in Magento 2.x
1. Require JS
If you’re building a high scale application and having the requirement to use multiple JavaScript libraries all together, like KO / JQuery / underscoreJS / nodeJS / ReactJS / BackboneJS / AngularJS, and
- If you need a platform to do asynchronous file loading,
- to organize the call back functions,
- wanted to maintain the co-ordination between JS components,
- wanted to data exchange between the JS components,
- set the right performance for the application,
- to avoid the head script include in phtml,
Then, the answer is RequireJS.
RequireJS-Config.js
- Requires-config.js is a piece of design from requireJS library.
- Each module can have its own Requires-config.js file.
- It has the entries for all JS files which are involved in particular module implementation.
- It has a specification to entry the files with Key – value pair.
Example :
'map': {
'*': {
'ko': 'knockoutjs/knockout', - Above example, ‘KO’ is a key for the value of knockout.js. Other JS components can refer knockout.js with key as ‘KO’.
2. Underscore JS
UnderscoreJS library provides the utility functions like foreach / isempty / duplicheck / invoke / index / String operations / and more like this. You need not write you’re won utility JS functions for your project. Simply you can take the help from the underscoreJS library. It has all the utility methods which you can use for your requirements.
3. JQery
You knew much better 😉
4. Knockout JS
KnockoutJS is another JS library which is having mechanism to implement the headless architecture. KO follows the MVVM pattern, which decouples the presentation layer and business layer. Let’s relate the components to V-MV-M [MVVM] on particularly checkout landing page.
M | Business logic layer [models / resource models / blocks] represents model. |
V | Onepage.phtml represents the view. |
V | .html represents view. Knockout uses .html template while render the response. |
M | Scope variable represents the Model. Scope variable holds the response object received from the onepage.php block. |
Main.JS
Main.JS will do following operations.
- Scan the HTML document and get all the response JSON objects for checkout page. Below for your reference.
- Further it will drill down and stitch the response Objects to data-bind variables on checkout page. Below for your information.
- Main.JS passes the data-bind objects to app.JS for the compilation.
Node | Content Sections |
---|---|
cookies | 1. domain |
2. expires | |
3. lifetime | |
4. path | |
5. secure | |
authenticationPopup | 1. authenticationPopup |
section-config | 1. baseUrls |
2. clientSideSections | |
3. sectionNames | |
4. sections | |
customer-data | 1. cart |
2. persistent | |
invalidation-processor | 1. invalidationRules |
pageCache | 1. handles |
2. originalRequest | |
3. URL | |
4. versionCookieName | |
checkout | 1. Authentication |
2. Progress Bar | |
3. Estimation | |
4. Messages | |
5. Steps | |
6. Sidebar | |
storage-manager | 1. product_data_storage |
2. recently_compared_product | |
3. recently_viewed_product |
HTML element | Bind variable | Response Object |
---|---|---|
div#authenticationPopup | data-bind="scope:'authenticationPopup'" | authenticationPopup |
div#checkout.checkout-container | data-bind="scope:'checkout'" | checkout |
app.JS
- Set the types values into store object. FYR below.
- App.JS passes the response object to layout.JS.
‘Types’ value from Respone | Which has a map in ‘checkout_index_index.xml’ |
---|---|
form.input | <item name="types" xsi:type="array"> <item name="form.input" xsi:type="array"> <item name="component" xsi:type="string">Magento_Ui/js/form/element/abstract</item> <item name="config" xsi:type="array"> <item name="provider" xsi:type="string">checkoutProvider</item> <item name="template" xsi:type="string">ui/form/field</item> <item name="elementTmpl" xsi:type="string">ui/form/element/input</item> </item> </item> </item> |
layout.JS
layout JS perform the following operations:
- Traverse through the ‘#checkout’ response object.
- Create the nested nodes and assign the required data by extracting the response object.
- Sample node is like below.
children:
flatrate-rates-validation:
component: "Magento_OfflineShipping/js/view/shipping-rates-validation/flatrate"
__proto__: Object
__proto__: Object
componentType: undefined
dataScope: ""
extendProvider: true
index: "shipping-rates-validation"
initChildCount: 1
name: "checkout.steps.shipping-step.step-config.shipping-rates-validation"
parentName: "checkout.steps.shipping-step.step-config"
parentScope: "" - Node will be set to the registry.JS. This component works like a bean to store the data as key – value pair and helps to exchange the data between JS components. Sample data populated Key-value pair below. Altered the value for precise.
- Control passes to Knockout JS.
Key | value |
---|---|
checkout.steps.shipping-step.shippingAddress.shipping-address-fieldset.firstname | component: "Magento_Ui/js/form/element/abstract" customScope: "shippingAddress" dataScope: "shippingAddress.firstname" elementTmpl: "ui/form/element/input" error: ƒ observable() errorId: "error-LS8LHFT" index: "firstname" initialValue: "Ramesh" inputName: "firstname" input_type: "input" label: "First Name" labelVisible: true links: {value: "checkoutProvider:shippingAddress.firstname"} listens: {visible: "setPreview", value: "setDifferedFromDefault", isUseDefault: "toggleUseDefault", checkoutProvider:data.reset: "reset", checkoutProvider:data.overload: "overload", …} name: "checkout.steps.shipping-step.shippingAddress.shipping-address-fieldset.firstname" parentName: "checkout.steps.shipping-step.shippingAddress.shipping-address-fieldset" parentScope: "shippingAddress" provider: "checkoutProvider" |
Knockout.JS
Knockout is a java script library. It plays in Magento a sophisticated role on field of view layer. The target mission is to compile the view model object.
What Knockout does here!
On high-level view
- Control over the HTML elements like, models / form / fields / attributes / CSS / JS.
- Knockout uses JS components like Engine / Loader / Render /I18 to execute below actions.
loader.JS
Loader JS prepare the complete path for .html based on the requested path.
Example:
Requested path: Magento_Checkout/summary/cart-items
Prepared template path: /template/summary/cart-items.html
render.JS
Render JS load the html with content.
cart-items.html
<div class="block items-in-cart" data-bind="mageInit: {'collapsible':{'openedState': 'active', 'active': isItemsBlockExpanded()}}"> <div class="title" data-role="title"> <strong role="heading" aria-level="1"> <translate args="maxCartItemsToDisplay" if="maxCartItemsToDisplay <getCartLineItemsCount()"/> <translate args="'of'" if="maxCartItemsToDisplay <getCartLineItemsCount()"/> <span data-bind="text: getCartSummaryItemsCount()"></span> <translate args="'Item in Cart'" if="getCartLineItemsCount() === 1"/> <translate args="'Items in Cart'" if="getCartLineItemsCount() > 1"/> </strong> </div> <div class="content minicart-items" data-role="content"> <div class="minicart-items-wrapper overflowed"> <ol class="minicart-items"> <each args="items()"> <li class="product-item"> <div class="product"> <each args="$parent.elems()" render=""/> </div> </li> </each> </ol> </div> </div> <div class="actions-toolbar" if="maxCartItemsToDisplay < getCartLineItemsCount()"> <div class="secondary"> <a class="action viewcart" data-bind="attr: {href: cartUrl}"> <span data-bind="i18n: 'View and Edit Cart'"></span> </a> </div> </div> </div>
- Knockout read or alters the bindings on .html template. Binding is like a place holder in HTML. Using placeholder’s KO control the data on html template at runtime.
- For below example, It reads the binding here,
- Initializes binding context (view model) for the particular node.
- ‘View model’ object populated with output values from the assigned JS component of the node.
- Here KO gets the ‘cart items summary count’ from the ‘/summary/cart-items.js’ component.
- JS component get the output values from the windows.checkoutconfig object. Flow is like this.
‘viewmodel’ -> cart-items.js -> window.checkoutconfig JS -> Quotedata -> items_qty -> 2 - Below is the end point at KO script to render the output as ‘2’ on page.
setDomNodeChildren: function (domNode, childNodes) { ko.utils.emptyDomNode(domNode); if (childNodes) { for (var i = 0, j = childNodes.length; i < j; i++) domNode.appendChild(childNodes[i]); } }
i18.JS
I18 component is an end point in control flow to show the static content in checkout page. Content is like, form lables / headings etc.
html flow for the shipping address form
The above cycle will be the same for execution of other sections in the page.
Thank You 👍
Share
Tags
Categories
Ramesh Lagisetty
Founder & CEO
Symphisys