Frontend development is a term most commonly tied to producing HTML, CSS, and JavaScript for a website or web application. Interchangeably, it addresses accessibility, usability, and performance toward reaching a satisfying user experience. Various levels of customization we want to apply to our web store require different development skill levels. We can make relatively simple changes to our store using just CSS. These would be the changes where we accept the structure of the store and focus only on visuals like changing colors and images. This might be a good starting point for less experienced developers and those new to the Magento platform. A more involved approach would be to make changes to the output generated by Magento modules. This usually means tiny bits of PHP knowledge, mostly copy-paste-modify of existing code fragments. A skill level above this one would imply knowledge of making structural changes to our store. This usually means mastering Magento's moderately sophisticated layout engine, where we make changes through XML definitions. The final and highest skill level for Magento frontend development implies the modification of existing or new custom functionality development.
Throughout this chapter, we will take a deep dive through the following sections:
The Magento application entry point is its index.php
file. All of the HTTP requests go through it.
Let's analyze the (trimmed) version of the index.php
file as follows:
//PART-1-1 require __DIR__ . '/app/bootstrap.php'; //PART-1-2 $bootstrap = MagentoFrameworkAppBootstrap::create(BP, $_SERVER); //PART-1-3 $app = $bootstrap-> createApplication('MagentoFrameworkAppHttp'); //PART-1-4 $bootstrap->run($app);
PART-1-1
of the preceding code simply includes /app/bootstrap.php
into the code. What happens inside the bootstrap is the inclusion of app/autoload.php
and app/functions.php
. The functions file contains a single __()
function, used for translation purposes, returning an instance of the MagentoFrameworkPhrase
object. Without going into the details of the auto-load file, it is suffice to say it handles the auto-loading of all our class files across Magento.
PART-1-2
is simply a static create method call to obtain the instance of the MagentoFrameworkAppBootstrap
object, storing it into the $bootstrap
variable.
PART-1-3
is calling the createApplication
method on the $bootstrap
object. What is happening within createApplication
is nothing more than using object manager to create and return the object instance of the class we are passing to it. Since we are passing the MagentoFrameworkAppHttp
class name to the createApplication
method, our $app
variable becomes the instance of that class. What this means, effectively, is that our web store app is an instance of MagentoFrameworkAppHttp
.
PART-1-4
is calling the run method on the $bootstrap
object, passing it the instance of the MagentoFrameworkAppHttp
class. Although it looks like a simple line of code, this is where things get complicated, as we will soon see.
Let's analyze the (trimmed) version of the MagentoFrameworkAppBootstrap
->
run
method as follows:
public function run(MagentoFrameworkAppInterface $application) { //PART-2-1 $this->initErrorHandler(); $this->initObjectManager(); $this->assertMaintenance(); $this->assertInstalled(); //PART-2-2 $response = $application->launch(); //PART-2-3 $response->sendResponse(); }
In the preceding code, PART-2-1
handles the sort of housekeeping bits. It initializes the custom error handler, initializes the object manager, checks if our application is in maintenance mode, and checks that it is installed.
PART-2-2
looks like a simple line of code. Here, we are calling the launch
method on $application
, which is the MagentoFrameworkAppHttp
instance. Without going into the inner workings of the launch
method for the moment, let's just say it returns the instance of the MagentoFrameworkAppResponseHttpInterceptor
class defined under var/generation/Magento/Framework/App/Response/Http/Interceptor.php
. Note that this is an automatically generated wrapper class, extending the MagentoFrameworkAppResponseHttp
class. Effectively, ignoring Interceptor
, we can say that $response
is an instance the MagentoFrameworkAppResponseHttp
class.
Finally, PART-2-3
calls the sendResponse
method on $response
. Though $response
is an instance of the MagentoFrameworkAppResponseHttp
class, the actual sendResponse
method is found further down the parent tree on the MagentoFrameworkHTTPPhpEnvironmentResponse
class. The sendResponse
method calls another parent class method called send
. The send
method can be found under the ZendHttpPhpEnvironmentResponse
class. It triggers the sendHeaders
and sendContent
methods. This is where the actual output gets sent to the browser, as the sendHeaders
method is using PHP's header
function and echo
construct to push the output.
To reiterate on the preceding, the flow of execution as we understand it comes down to the following:
index.php
MagentoFrameworkAppBootstrap
->
run
MagentoFrameworkAppHttp
->
launch
MagentoFrameworkAppResponseHttp
->
sendResponse
Though we have just made it to the end of the bootstrap's run
method, it would be unfair to say we covered the rendering flow, as we barely touched it.
We need to take a step back and take a detailed look at PART-2-2
, the inner workings of the launch
method. Let's take a look at the (trimmed) version of the MagentoFrameworkAppHttp
->
launch
method as follows:
public function launch() { //PART-3-1 $frontController = $this->_objectManager->get ('MagentoFrameworkAppFrontControllerInterface'); //PART-3-2 $result = $frontController->dispatch($this->_request); if ($result instanceof MagentoFrameworkController ResultInterface) { //PART-3-3 $result->renderResult($this->_response); } elseif ($result instanceof MagentoFrameworkApp ResponseHttpInterface) { $this->_response = $result; } else { throw new InvalidArgumentException('Invalid return type'); } //PART-3-4 return $this->_response; }
PART-3-1
creates the instance of the object whose class conforms to MagentoFrameworkAppFrontControllerInterface
. If we look under app/etc/di.xml
, we can see there is a preference for FrontControllerInterface
in favor of the MagentoFrameworkAppFrontController
class. However, if we were to debug the code and check for the actual instance class, it would show MagentoFrameworkAppFrontControllerInterceptor
. This is Magento adding an interceptor wrapper that then extends MagentoFrameworkAppFrontController
, which we expected from the di.xml
preference entry.
Now that we know the real class behind the $frontController
instance, we know where to look for the dispatch
method. The dispatch
method is another important step in understanding the rendering flow process. We will look into its inner workings in a bit more detail later on. For now, let's focus back on the $result
variable of PART-3-2
. If we were to debug the variable, the direct class behind it would show as MagentoFrameworkViewResultPageInterceptor
, defined under the dynamically created var/generation/Magento/Framework/View/Result/Page/Interceptor.php
file. Interceptor
is the wrapper for the MagentoFrameworkViewResultPage
class. Thus, it is safe to say that our $result
variable is an instance of the Page
class.
The Page
class extends MagentoFrameworkViewResultLayout
, which further extends MagentoFrameworkControllerAbstractResult
and implements MagentoFrameworkControllerResultInterface
. Quite a chain we have here, but it is important to understand it.
Notice PART-3-3
. Since our $result
is an instance of MagentoFrameworkControllerResultInterface
, we fall into the first if
condition that calls the renderResult
method. The renderResult
method itself is declared within the MagentoFrameworkViewResultLayout
class. Without going into the details of renderResult
, suffice to say that it adds HTTP headers, and content to the $this->_response
object passed to it. That same response object is what the launch
method returns, as we described before in PART-2-2
.
Though PART-3-3
does not depict any return value, the expression $result->renderResult($this->_response)
does not do any output on its own. It modifies $this->_response
that we finally return from the launch
method as shown in PART-3-4
.
To reiterate on the preceding, the flow of execution as we understand it comes down to the following:
index.php
MagentoFrameworkAppBootstrap
->
run
MagentoFrameworkAppHttp
->
launch
MagentoFrameworkAppFrontController
->
dispatch
MagentoFrameworkViewResultPage
->
renderResult
MagentoFrameworkAppResponseHttp
->
sendResponse
As we mentioned while explaining PART-3-2
, the dispatch
method is another important step in the rendering flow process. Let's take a look at the (trimmed) version of the MagentoFrameworkAppFrontController
->
dispatch
method as follows:
public function dispatch(MagentoFrameworkAppRequestInterface $request) { //PART-4-1 while (!$request->isDispatched() && $routingCycleCounter++ < 100) { //PART-4-2 foreach ($this->_routerList as $router) { try { //PART-4-3 $actionInstance = $router->match($request); if ($actionInstance) { $request->setDispatched(true); //PART-4-4 $result = $actionInstance->dispatch($request); break; } } catch (MagentoFrameworkException NotFoundException $e) {} } } //PART-4-4 return $result; }
PART-4-1
and PART-4-2
in the preceding code shows (almost) the entire dispatch
method body contained within a loop. The loop does 100 iterations, further looping through all available router types, thus giving each router 100 times to find a route match
.
The router list loop includes routers of the following class types:
MagentoFrameworkAppRouterBase
MagentoUrlRewriteControllerRouter
MagentoCmsControllerRouter
MagentoFrameworkAppRouterDefaultRouter
All of the listed routers implement MagentoFrameworkAppRouterInterface
, making them all have the implementation of the match
method.
A module can further define new routers if they choose so. As an example, imagine if we are developing a Blog
module. We would want our module catching all requests on a URL that starts with a /blog/
part. This can be done by specifying the custom router, which would then show up on the preceding list.
PART-4-3
shows the $actionInstance
variable storing the result of the router match
method call. As per RouterInterface
requirements, the match
method is required to return an instance whose class implements MagentoFrameworkAppActionInterface
. Let's imagine we are now hitting the URL /foggyline_office/test/crud/
from the module we wrote in Chapter 4, Models and Collections. In this case, our $router
class would be MagentoFrameworkAppRouterBase
and our $actionInstance
would be of the class FoggylineOfficeControllerTestCrudInterceptor
. Magento automatically adds Interceptor
, through the dynamically generated var/generation/Foggyline/Office/Controller/Test/Crud/Interceptor.php
file. This Interceptor
class further extends our module FoggylineOfficeControllerTestCrud
class file. The Crud
class extends FoggylineOfficeControllerTest
, which further extends MagentoFrameworkAppActionAction
, which implements MagentoFrameworkAppActionInterface
. After a lengthy parent-child tree, we finally got to ActionInterface
, which is what our match
method is required to return.
PART-4-4
shows the dispatch
method being called on $actionInstance
. This method is implemented within MagentoFrameworkAppActionAction
, and is expected to return an object that implements MagentoFrameworkAppResponseInterface
. Internal to dispatch
, the execute
method is called, thus running the code within our Crud
controller action execute
method.
Assuming our Crud
controller action execute method does not return nothing, the $result
object becomes an instance of MagentoFrameworkAppResponseHttpInterceptor
, which is wrapped around MagentoFrameworkAppResponseHttp
.
Let's imagine our Crud
class has been defined as follows:
/** * @var MagentoFrameworkViewResultPageFactory */ protected $resultPageFactory; public function __construct( MagentoFrameworkAppActionContext $context, MagentoFrameworkViewResultPageFactory $resultPageFactory ) { $this->resultPageFactory = $resultPageFactory; return parent::__construct($context); } public function execute() { $resultPage = $this->resultPageFactory->create(); //... return $resultPage; }
Debugging the $result
variable now shows it's an instance of MagentoFrameworkViewResultPageInterceptor
. This Interceptor
gets dynamically generated by Magento under var/generation/Magento/Framework/View/Result/Page/Interceptor.php
and is merely a wrapper for MagentoFrameworkViewResultPage
. This Page
class further extends the MagentoFrameworkViewResultLayout
class, and implements MagentoFrameworkAppResponseInterface
.
Finally, PART-4-4
shows the $result
object of type MagentoFrameworkViewResultPage
being returned from the FrontController
dispatch
method.
To reiterate on the preceding, the flow of execution as we understand it comes down to the following:
index.php
MagentoFrameworkAppBootstrap
->
run
MagentoFrameworkAppHttp
->
launch
MagentoFrameworkAppFrontController
->
dispatch
MagentoFrameworkAppRouterBase
->
match
MagentoFrameworkAppActionAction
->
dispatch
MagentoFrameworkViewResultPage
->
renderResult
MagentoFrameworkAppResponseHttp
->
sendResponse
In a nutshell, what we as frontend developers should know is that returning the Page type object from our controller action will automatically call the renderResult
method on that object. Page
and Layout
is where all the theme translations, layout, and template loading are triggering.
3.147.42.168