Magento 2 – Request Flow (Bootstrapping)

Primeiramente meu olá à você que está lendo este artigo.

Acredito que se você chegou até aqui esteja interessado em aprender um pouco mais sobre o Magento 2. Pois bem, eu também estou interessadíssimo em aprender sempre mais sobre o Magento 2.

O Magento 1 se tornou, entre todos estes anos, a principal plataforma de e-commerce do mercado mundial e isso chamou muito a atenção de nós, desenvolvedores, e de profissionais que trabalham, direta ou indiretamente, com o Magento. Chamou tanto a atenção que agora o mercado está se preparando para usar fortemente a nova versão do Magento Commerce, a versão 2, e nós, desenvolvedores, precisamos estar sempre atualizados sobre as novidades em torno do Magento para podermos utilizá-las sempre que necessário, seja para um freela ou para um projeto em sua empresa.

Pelo pouco que estudei a plataforma pude perceber que ela é simplesmente sensacional em alguns quesitos o muito atualizada em relação ás plataformas do mercado atualmente. Muitas das novidades do mercado de desenvolvimento web estão implementadas na plataforma como, por exemplo, o uso de composer para gerenciar as dependências PHP do seu projeto, a utilização dos namespaces, disponíveis desde a versão 5.3 do PHP, a inclusão de jQuery como biblioteca de javascript nativa, a possibilidade da utilização das Traits, disponíveis desde a versão 5.4 do PHP, enfim, se você quiser saber um pouco mais sobre algumas das diferenças, você pode ler este post.

Neste post quero falar apenas sobre o Request Flow do Magento 2.

O que é Request Flow?

Request Flow, ou em português Fluxo de Requisição, que também pode ser conhecido como Application Bootstrap, nada mais é do que o fluxo da requisição dentro da aplicação, ou seja, é todo o processo que ocorre dentro da plataforma desde quando ela recebe a requisição (chamada de URL) pelo arquivo index.php, responsável por inicializar a aplicação, até o momento que o usuário pode ver o resultado da página no navegador.

Aonde Tudo Começa

Como dito logo acima, assim como é em várias outras plataformas e frameworks web, tudo se inicia pelo arquivo index.php:

<?php
/**
* Application entry point
*
* Example - run a particular store or website:
* --------------------------------------------
* require __DIR__ . '/app/bootstrap.php';
* $params = $_SERVER;
* $params[\Magento\Store\Model\StoreManager::PARAM_RUN_CODE] = 'website2';
* $params[\Magento\Store\Model\StoreManager::PARAM_RUN_TYPE] = 'website';
* $bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $params);
* \/** @var \Magento\Framework\App\Http $app *\/
* $app = $bootstrap->createApplication('Magento\Framework\App\Http');
* $bootstrap->run($app);
* --------------------------------------------
*
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
try {
require __DIR__ . '/app/bootstrap.php';
} catch (\Exception $e) {
echo <<<HTML
<div style="font:12px/1.35em arial, helvetica, sans-serif;">
<div style="margin:0 0 25px 0; border-bottom:1px solid #ccc;">
<h3 style="margin:0;font-size:1.7em;font-weight:normal;text-transform:none;text-align:left;color:#2f2f2f;">
Autoload error</h3>
</div>
<p>{$e->getMessage()}</p>
</div>
HTML;
exit(1);
}
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);
/** @var \Magento\Framework\App\Http $app */
$app = $bootstrap->createApplication('Magento\Framework\App\Http');
$bootstrap->run($app);
view raw index.php hosted with ❤ by GitHub

Perceba que no arquivo index.php, mais precisamente na linha 22, é chamado o arquivo app/bootstrap.php que tem a responsabilidade de incluir os arquivos app/autoload.php e app/functions.php e configurar o timezone padrão do Magento para UTC:

<?php
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
/**
* Environment initialization
*/
error_reporting(E_ALL);
#ini_set('display_errors', 1);
umask(0);
/* PHP version validation */
if (version_compare(phpversion(), '5.5.0', '<') === true) {
if (PHP_SAPI == 'cli') {
echo 'Magento supports PHP 5.5.0 or later. ' .
'Please read http://devdocs.magento.com/guides/v1.0/install-gde/system-requirements.html&#39;;
} else {
echo <<<HTML
<div style="font:12px/1.35em arial, helvetica, sans-serif;">
<p>Magento supports PHP 5.5.0 or later. Please read
<a target="_blank" href="http://devdocs.magento.com/guides/v1.0/install-gde/system-requirements.html"&gt;
Magento System Requirements</a>.
</div>
HTML;
}
exit(1);
}
require_once __DIR__ . '/autoload.php';
require_once BP . '/app/functions.php';
if (!empty($_SERVER['MAGE_PROFILER'])
&& isset($_SERVER['HTTP_ACCEPT'])
&& strpos($_SERVER['HTTP_ACCEPT'], 'text/html') !== false
) {
\Magento\Framework\Profiler::applyConfig(
$_SERVER['MAGE_PROFILER'],
BP,
!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
);
}
date_default_timezone_set('UTC');
view raw bootstrap.php hosted with ❤ by GitHub

O arquivo app/autoload.php tem a responsabilidade de fazer a inclusão de todos os arquivos necessários para o autoload das classes dentro da pasta vendor para que tudo funcione corretamente no momento de instanciar novos objetos. Já o arquivo app/functions.php é responsável por adicionar o método de tradução __ (isso mesmo, são dois underscores) que tanto conhecemos no Magento 1.

Com o autoload inicializado, o próximo passo é criar uma instância do bootstrap:

...

$bootstrap = MagentoFrameworkAppBootstrap::create(BP, $_SERVER);

...

Esta chamada faz com que o ObjectManager do Magento seja iniciado. Não entrarei em detalhes sobre o que é o ObjectManager no Magento 2 neste post, vale á pena dedicar um post só para isso, e é o que farei.

O próximo passo é criar uma instância da classe MagentoFrameworkAppHttp, substituto da classe Mage_Core_Model_App do Magento 1, e chamar o método run, passando como referência a classe MagentoFrameworkAppHttp criada:

...

/** @var MagentoFrameworkAppHttp $app */
$app = $bootstrap->createApplication('MagentoFrameworkAppHttp');
$bootstrap->run($app);

...

Agora todo o controle da inicialização da aplicação está nas mãos da classe MagentoFrameworkAppBootstrap.

<?php
...
/**
* Runs an application
*
* @param \Magento\Framework\AppInterface $application
* @return void
*/
public function run(AppInterface $application)
{
try {
try {
\Magento\Framework\Profiler::start('magento');
$this->initErrorHandler();
$this->initObjectManager();
$this->assertMaintenance();
$this->assertInstalled();
$response = $application->launch();
$response->sendResponse();
\Magento\Framework\Profiler::stop('magento');
} catch (\Exception $e) {
\Magento\Framework\Profiler::stop('magento');
if (!$application->catchException($this, $e)) {
throw $e;
}
}
} catch (\Exception $e) {
$this->terminate($e);
}
}
...

Este método segue os seguintes passos:

Inicializa um profiler:

...

MagentoFrameworkProfiler::start('magento');

...

Inicializa a classe responsável por tratamento de erros:

...

$this->initErrorHandler();

...

Inicializa o ObjectManager, caso o mesmo não esteja inicializado:

...

$this->initObjectManager();

...

Verifica se o Magento está em estado de manutenção:

...

$this->assertMaintenance();

...

Verifica se o Magento já está instalado e se a instalação está correta:

...

$this->assertInstalled();

...

E finalmente inicia a aplicação:

...

$response = $application->launch();

...

Podemos ver o método launch() melhor aqui:

<?php
...
/**
* Run application
*
* @throws \InvalidArgumentException
* @return ResponseInterface
*/
public function launch()
{
$areaCode = $this->_areaList->getCodeByFrontName($this->_request->getFrontName());
$this->_state->setAreaCode($areaCode);
$this->_objectManager->configure($this->_configLoader->load($areaCode));
/** @var \Magento\Framework\App\FrontControllerInterface $frontController */
$frontController = $this->_objectManager->get('Magento\Framework\App\FrontControllerInterface');
$result = $frontController->dispatch($this->_request);
// TODO: Temporary solution until all controllers return ResultInterface (MAGETWO-28359)
if ($result instanceof ResultInterface) {
$this->registry->register('use_page_cache_plugin', true, true);
$result->renderResult($this->_response);
} elseif ($result instanceof HttpInterface) {
$this->_response = $result;
} else {
throw new \InvalidArgumentException('Invalid return type');
}
// This event gives possibility to launch something before sending output (allow cookie setting)
$eventParams = ['request' => $this->_request, 'response' => $this->_response];
$this->_eventManager->dispatch('controller_front_send_response_before', $eventParams);
return $this->_response;
}
...

Neste método, se tratando do principal do request flow, duas linhas se destacam:

...
/** @var MagentoFrameworkAppFrontControllerInterface $frontController */
$frontController = $this-&amp;amp;amp;amp;amp;amp;amp;gt;_objectManager-&amp;amp;amp;amp;amp;amp;amp;gt;get('MagentoFrameworkAppFrontControllerInterface');
$result = $frontController-&amp;amp;amp;amp;amp;amp;amp;gt;dispatch($this-&amp;amp;amp;amp;amp;amp;amp;gt;_request);
...

Estas linhas são responsáveis por instanciar a classe MagentoFrameworkAppFrontController, localizada em lib/internal/Magento/Framework/App/FrontController.php e chamar o método dispatch() da mesma:

<?php
/**
* Front controller responsible for dispatching application requests
*
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Framework\App;
class FrontController implements FrontControllerInterface
{
/**
* @var RouterList
*/
protected $_routerList;
/**
* @var \Magento\Framework\App\Response\Http
*/
protected $response;
/**
* @param RouterList $routerList
* @param \Magento\Framework\App\Response\Http $response
*/
public function __construct(
RouterList $routerList,
\Magento\Framework\App\Response\Http $response
) {
$this->_routerList = $routerList;
$this->response = $response;
}
/**
* Perform action and generate response
*
* @param RequestInterface $request
* @return ResponseInterface|\Magento\Framework\Controller\ResultInterface
* @throws \LogicException
*/
public function dispatch(RequestInterface $request)
{
\Magento\Framework\Profiler::start('routers_match');
$routingCycleCounter = 0;
$result = null;
while (!$request->isDispatched() && $routingCycleCounter++ < 100) {
/** @var \Magento\Framework\App\RouterInterface $router */
foreach ($this->_routerList as $router) {
try {
$actionInstance = $router->match($request);
if ($actionInstance) {
$request->setDispatched(true);
$this->response->setNoCacheHeaders();
if ($actionInstance instanceof \Magento\Framework\App\Action\AbstractAction) {
$result = $actionInstance->dispatch($request);
} else {
$result = $actionInstance->execute();
}
break;
}
} catch (\Magento\Framework\Exception\NotFoundException $e) {
$request->initForward();
$request->setActionName('noroute');
$request->setDispatched(false);
break;
}
}
}
\Magento\Framework\Profiler::stop('routers_match');
if ($routingCycleCounter > 100) {
throw new \LogicException('Front controller reached 100 router match iterations');
}
return $result;
}
}

Routers

Um Router, em poucas palavras, é uma classe responsável por analisar e processar uma requisição. Assim como no Magento 1, no Magento 2 temos, por padrão, alguns Routers configurados para a aplicação funcionar: Base Router, Default Router, Cms Router e UrlRewrite Router. Vamos falar um pouco sobre seus propósitos e como cada uma funciona. Basicamente a sequência de leitura dos routers é:

Base Router → CMS Router → UrlRewrite Router → Default Router

Base Router

Localizado em lib/internal/Magento/Framework/App/Router/Base.php, é o primeiro router do loop e se você é um desenvolvedor Magento 1 saberá que este é basicamente o Standard Router, responsável por rotear as rotas standards, ou seja, as rotas definidas para o frontend da aplicação, como por exemplo a roda customer/account/login.

CMS Router

Localizada em app/code/Magento/Cms/Controller/Router.php, é utilizada para gerenciar as rotas das páginas CMS, e definir o nome do módulo para cms, nome do controller para page e o nome da action para viewapp/code/Magento/Cms/Controller/Page/View.php. Após estas definições, irá definir o parâmetro page_id e então encaminhar a requisição de volta para o Base Router para que o mesmo faça o dispatch da rota corretamente. Isso devido o Base Router só conseguir mapear corretamente rotas no formato modulo/controller/view, portanto quem precisa deixar a rota neste formato é o CMS Router.

UrlRewrite Router

No Magento 2 o UrlRewrite tem seu próprio Router, se você já conhece o Magento 1 saberá que URL Rewrite fazia parte do Standard Router. Está localizado em app/code/Magento/UrlRewrite/Controller/Router.php e utiliza o Url Finder para definir as rotas de URL Rewrite do banco dados. Após a definição da rota, assim como o CMS Router, O UrlRewrite Router faz um forward da requisição para que o Base Router faça o dispatch.

Default Router

Está localizado em lib/internal/Magento/Framework/App/Router/DefaultRouter.php e é o último router do laço. É utilizado apenas quando nenhum dos outros routers conseguem fazer o match da rota. No Magento 2 nós podemos criar handles personalizados para páginas “Não encontradas” e assim mostrar um conteúdo personalizado.

Dispatch de Routers

Quando o Magento chega neste ponto da aplicação, os Routers são iterados e cada um deles irá tentar fazer o dispatch da rota corretamente, ou seja, cada um com sua responsabilidade, tentará encontrar o Controller Action responsável pela lógica da regra de negócio daquela requisição, por exemplo, quando a rota chamada for customer/account/login o Controller Action responsável pela regra de negócio será MagentoCustomerControllerAccountLogin, localizada em app/code/Magento/Customer/Controller/Account/Login.php.

A partir deste momento o fluxo da requisição fica nas mãos do Controller Action do módulo responsável, conforme o exemplo acima, e o mesmo tem como responsabilidade de entregar o resultado da requisição. Podemos ver que o resultado é salvo em uma variável $result que é uma instância da classe MagentoFrameworkViewResultPage que, por sua vez, extende a classe MagentoFrameworkViewResultLayout. Quem for desenvolvedor Magento 1 saberá que este objeto é responsável por fazer a renderização dos Layout Blocks, que nada mais são do que o conteúdo da página montada como um quebra-cabeça.

Então o método renderResult é chamado para fazer a renderização dos blocos:

...
/** @class MagentoFrameworkViewResultLayout **/

/**
* Render current layout
*
* @param ResponseInterface $response
* @return $this
*/
public function renderResult(ResponseInterface $response)
{
MagentoFrameworkProfiler::start('LAYOUT');
MagentoFrameworkProfiler::start('layout_render');

$this->applyHttpHeaders($response);
$this->render($response);

$this->eventManager-&gt;dispatch('layout_render_before');
$this->eventManager-&gt;dispatch('layout_render_before_' . $this->request->getFullActionName());
MagentoFrameworkProfiler::stop('layout_render');
MagentoFrameworkProfiler::stop('LAYOUT');
return $this;
}

...

Retornando o objeto MagentoFrameworkAppResponseHttp para o método MagentoFrameworkAppBootstrap::run() (lembra dele?) e o mesmo simplesmente envia a resposta ao navegador do usuário:

/* ... */

public function run(AppInterface $application)
{

/* ... */
$response->sendResponse();
/* ... */

}

/* ... */

Este é o fluxo base do Request Flow do Magento 2. É realmente bastante parecido com o Request Flow do Magento 1, com conceitos muito próximos.

Aproveitem para dar uma olhada no código e estudar um pouco mais sobre o Magento 2.

Grande abraço,

– Tiago

2 thoughts on “Magento 2 – Request Flow (Bootstrapping)

  1. Olá tiago olhei sua pagina acredito que possa me ajudar
    Hoje estou montando minha loja magento V 2.1.4 e esta neste provisorio dominio . americanbrasil.lpcdev.com.br que é de um desenvolvedor .
    Estou com algumas dificuldades em encontrar APi Correios que funcione corretamente e Demais Api .
    Será que com sua experiencia poderia me indicar algum local onde eu possa encontrar varias Api podem ser pagas não tem problema .

    Preciso de um ERP tambem , aguardo desde já suas respostas .

    Att
    ilcimar Canibal

    Like

Leave a comment