This is quick, high level, possibly incorrect, overview of how Magento 2 runs applications, and what the kernel of its system architecture looks like. This is the level above MVC/MVVM, and will only be of interest to folks (like me) who need to know how everything works.
From a high level point of view, Magento 2 is an abstract operating system for creating PHP applications, or what I’m calling “Magento System Applications”.
Magento 2’s top level architecture is relatively simple.
First, your web server (apache, nginx) routes PHP requests to a single PHP script/entry point. This PHP script is responsible for
- Loading a PHP bootstrap script. This bootstrap script normalizes the PHP environment (including requiring in the composer autoloader)
- Creating a
MagentoFrameworkAppBootstrap
object -
Creating the Magento System Application object using the
MagentoFrameworkAppBootstrap
object -
Using the
MagentoFrameworkAppBootstrap
object to run the Magento System Application
A Magento system application is a PHP object with a launch
method. When the MagentoFrameworkAppBootstrap
class run
s an application, it ultimately calls this launch
method. An application’s launch
method is responsible for returning a response object. This response object has no explicit interface, but has an implied interface with a single public method of sendResponse
.
After calling the Magento System Application object’s launch
method, the MagentoFrameworkAppBootstrap
object will call the returned response object’s sendResponse
method. This method is responsible for echoing any output to the end user.
The MagentoFrameworkAppBootstrap
object provides the Magento System Application with a root path, a set of parameters ($_SERVER variables), and an initialized object manager. This object manager is initialized with every module’s etc/di.xml
files.
Magento System Application developers have the option of setting an area on the application during launch. Setting an area will ensure requests for configuration trees include files located in
app/etc/[area]/*.xml
If Magento System Application developers want their object manager to include files from the specific area configuration, they’ll need to load that configuration into the object separately (as the MagentoFrameworkAppBootstrap
object already loads the default etc/di.xml
when it provides the instantiated object manager)
Example: Main Application Entry Point
The first obvious example is Magento’s main index.php
entry point.
First, Magento’s .htaccess
ensures everything is sent to index.php
############################################
## never rewrite for existing files, directories and links
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
############################################
## rewrite everything else to index.php
RewriteRule .* index.php [L]
</IfModule>
############################################
Then, in index.php
try {
require __DIR__ . '/app/bootstrap.php';
} catch (Exception $e) {
//...
exit(1);
}
$bootstrap = MagentoFrameworkAppBootstrap::create(BP, $_SERVER);
/** @var MagentoFrameworkAppHttp $app */
$app = $bootstrap->createApplication('MagentoFrameworkAppHttp');
$bootstrap->run($app);
We see the app/bootstrap.php
file require
d in.
Then the script creates a MagentoFrameworkAppBootstrap
object. Then it creates a MagentoFrameworkAppHttp
object using the createApplication
method, and then runs the Magento System Application object by passing it in to the Bootstrap
object’s run method.
In the application object’s launch method, we see the top level code that implements Magento 2’s MVC/MVVM system, including setting the application area, re-configuring the object manager so it includes area specific di.xml
, and returning a response object.
#File: vendor/magento/framework/App/Http.php
public function launch()
{
$areaCode = $this->_areaList->getCodeByFrontName($this->_request->getFrontName());
$this->_state->setAreaCode($areaCode);
$this->_objectManager->configure($this->_configLoader->load($areaCode));
/** @var MagentoFrameworkAppFrontControllerInterface $frontController */
$frontController = $this->_objectManager->get('MagentoFrameworkAppFrontControllerInterface');
$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;
}
Example: Serving Static Asset Files
Another example is how Magento 2 serves static asset files. A static asset file is a CSS, Javascript, etc. file whose URL takes the form.
http://magento.example.com/static/frontend/Magento/luma/en_US/mage/calendar.css
http://magento.example.com/pub/static/frontend/Magento/luma/en_US/mage/calendar.css
First, if we look at the pub/static
folder’s .htaccess
file
#File: pub/static/.htaccess
<IfModule mod_rewrite.c>
RewriteEngine On
# Remove signature of the static files that is used to overcome the browser cache
RewriteRule ^version.+?/(.+)$ $1 [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule .* ../static.php?resource=$0 [L]
</IfModule>
We see all requests are rewritten to the pub/static.php
file. We also see the original path is passed as a resource
query string parameter.
If we take a look at pub/static.php
#File: pub/static.php
require __DIR__ . '/../app/bootstrap.php';
$bootstrap = MagentoFrameworkAppBootstrap::create(BP, $_SERVER);
/** @var MagentoFrameworkAppStaticResource $app */
$app = $bootstrap->createApplication('MagentoFrameworkAppStaticResource');
$bootstrap->run($app);
We, again, first see that bootstrap.php
gets require
d in.
Then, the script creates a MagentoFrameworkAppBootstrap
object.
Then, the script uses the MagentoFrameworkAppBootstrap
object to create a Magento System Application object – this time a MagentoFrameworkAppStaticResource
object.
Then, same as before, the MagentoFrameworkAppBootstrap
object runs the application object.
If we take a look at this application object’s launch
method
#File: vendor/magento/framework/App/StaticResource.php
public function launch()
{
// disabling profiling when retrieving static resource
MagentoFrameworkProfiler::reset();
$appMode = $this->state->getMode();
if ($appMode == MagentoFrameworkAppState::MODE_PRODUCTION) {
$this->response->setHttpResponseCode(404);
} else {
$path = $this->request->get('resource');
$params = $this->parsePath($path);
$this->state->setAreaCode($params['area']);
$this->objectManager->configure($this->configLoader->load($params['area']));
$file = $params['file'];
unset($params['file']);
$asset = $this->assetRepo->createAsset($file, $params);
$this->response->setFilePath($asset->getSourceFile());
$this->publisher->publish($asset);
}
return $this->response;
}
We see a slightly simpler launch
. This method still returns a response object, but instead of kicking off a MVC/MVVM front controller, this object uses the passed in resource
variable to find the static asset file, and serve it. You can also see the launch
method parses the area name (frontend
, adminhtml
) from that path, before setting it for the application and re-configuring the object manager
$path = $this->request->get('resource');
$params = $this->parsePath($path);
$this->state->setAreaCode($params['area']);
$this->objectManager->configure($this->configLoader->load($params['area']));
The MagentoFrameworkAppStaticResource
Magento System Application is what allows us to store our static assets in our module folders, but still have them load from public URLs.