Data output to the browser
Data for output to the browser accumulates in the controller in the internal variable Mage_Core_Controller_Response_Http::_body, and is displayed by calling Mage_Core_Controller_Response_Http::appendBody(). In most cases, processing is carried out through the Layout object and is ended by calling $this->renderLayout():
/app/code/core/Mage/Core/Controller/Varien/Action.php
1 2 3 4 5 6 7 8 9 10 11 12 |
public function renderLayout($output='') { … $this->getLayout()->setDirectOutput(false); $output = $this->getLayout()->getOutput(); Mage::getSingleton('core/translate_inline')->processResponseBody($output); $this->getResponse()->appendBody($output); Varien_Profiler::stop("$_profilerKey::layout_render"); return $this; } |
The line $this->getLayout()->setDirectOutput(false); includes output buffering.
For calls in which Layout is not used, for example, via Ajax, content is set directly by calling $this->getResponse()->appendBody($output); :
1 2 3 4 5 6 7 8 |
public function ajaxAction() { // generate $json string …. // set output $this->getResponse()->setHeader('Content-type', 'application/x-json'); $this->getResponse()->appendBody($json); } |
Partner With Us
Let's discuss how to grow your business. Get a Free Quote.After executing the controller, direct output to the browser is carried out:
/app/code/core/Mage/Core/Controller/Varien/Front.php
1 2 3 4 5 6 7 8 9 10 |
public function dispatch() { … // This event gives the possibility to launch something before sending output (allow cookie setting) Mage::dispatchEvent('controller_front_send_response_before', array('front'=>$this)); $this->getResponse()->sendResponse(); Varien_Profiler::stop('mage::app::dispatch::send_response'); Mage::dispatchEvent('controller_front_send_response_after', array('front'=>$this)); return $this; } |
Before the output of data to the browser the event controller_front_send_response_before is run, after it – controller_front_send_response_after. The event controller_front_send_response_before may contain additional headers, that is why it is important to use the method $this->getResponse()-> appendBody() instead of the simple echo output in the controller. Otherwise, the headers installed after the controller will not be applied.
Let us take a close look at Zend_Controller_Response_Http::sendResponse public function sendResponse()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ $this->sendHeaders(); if ($this->isException() && $this->renderExceptions()) { $exceptions = ''; foreach ($this->getException() as $e) { $exceptions .= $e->__toString() . "\n"; } echo $exceptions; return; } $this->outputBody(); } |
First, the function sendHeaders() applies all the standard headers, accumulated through the call $this->getResponse()->setHeader();. Next, the error handling in the content output is performed:
1 2 3 4 5 |
public function outputBody() { $body = implode('', $this->_body); echo $body; } |
Redirect
There are two main methods for redirect in the controller:
- _redirect();
- _forward();
_redirect method performs an external redirect, i.e. redirects the user to a new URL setting http header Location and passes the session.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
protected function _redirect($path, $arguments = array()) { return $this->setRedirectWithCookieCheck($path, $arguments); } public function setRedirectWithCookieCheck($path, array $arguments = array()) { /** @var $session Mage_Core_Model_Session */ $session = Mage::getSingleton('core/session', array('name' => $this->_sessionNamespace)); if ($session->getCookieShouldBeReceived() && Mage::app()->getUseSessionInUrl() && $this->_sessionNamespace != Mage_Adminhtml_Controller_Action::SESSION_NAMESPACE ) { $arguments += array('_query' => array( $session->getSessionIdQueryParam() => $session->getSessionId() )); } $this->getResponse()->setRedirect(Mage::getUrl($path, $arguments)); return $this; } |
_forward method performs an internal redirect in Magento, invisible to the user. _forward method can redirect execution to another module, controller and action:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
protected function _forward($action, $controller = null, $module = null, array $params = null) { $request = $this->getRequest(); $request->initForward(); if (isset($params)) { $request->setParams($params); } if (isset($controller)) { $request->setControllerName($controller); // Module should only be reset if controller has been specified if (isset($module)) { $request->setModuleName($module); } } $request->setActionName($action) ->setDispatched(false); } |
Combining Javascript and CSS files
Output of Javascript and CSS files is carried out by calling Mage_Page_Block_Html_Head::getCssJsHtml().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
public function getCssJsHtml() { // separate items by types $lines = array(); foreach ($this->_data['items'] as $item) { if (!is_null($item['cond']) && !$this->getData($item['cond']) || !isset($item['name'])) { continue; } $if = !empty($item['if']) ? $item['if'] : ''; $params = !empty($item['params']) ? $item['params'] : ''; switch ($item['type']) { case 'js': // js/*.js case 'skin_js': // skin/*/*.js case 'js_css': // js/*.css case 'skin_css': // skin/*/*.css $lines[$if][$item['type']][$params][$item['name']] = $item['name']; break; default: $this->_separateOtherHtmlHeadElements($lines, $if, $item['type'], $params, $item['name'], $item); break; } } // prepare HTML $shouldMergeJs = Mage::getStoreConfigFlag('dev/js/merge_files'); $shouldMergeCss = Mage::getStoreConfigFlag('dev/css/merge_css_files'); $html = ''; foreach ($lines as $if => $items) { if (empty($items)) { continue; } if (!empty($if)) { $html .= '<!--[if '.$if.']>'."\n"; } // static and skin css $html .= $this->_prepareStaticAndSkinElements('<link rel="stylesheet" type="text/css" href="%s"%s />' . "\n", empty($items['js_css']) ? array() : $items['js_css'], empty($items['skin_css']) ? array() : $items['skin_css'], $shouldMergeCss ? array(Mage::getDesign(), 'getMergedCssUrl') : null ); // static and skin javascripts $html .= $this->_prepareStaticAndSkinElements('<script type="text/javascript" src="%s"%s></script>' . "\n", empty($items['js']) ? array() : $items['js'], empty($items['skin_js']) ? array() : $items['skin_js'], $shouldMergeJs ? array(Mage::getDesign(), 'getMergedJsUrl') : null ); // other stuff if (!empty($items['other'])) { $html .= $this->_prepareOtherHtmlHeadElements($items['other']) . "\n"; } if (!empty($if)) { $html .= '<![endif]-->'."\n"; } } return $html; } |
Array $this->_data[‘items’] contains the list of add-on CSS and JavaScript files and is formed by calling addItem in Layout. In /app/design/frontend/base/default/layout/page.xml the following can be found:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
… <block type="page/html_head" name="head" as="head"> <action method="addJs"><script>prototype/prototype.js</script></action> <action method="addJs"><script>lib/ccard.js</script></action> <action method="addJs"><script>prototype/validation.js</script></action> <action method="addJs"><script>scriptaculous/builder.js</script></action> <action method="addJs"><script>scriptaculous/effects.js</script></action> <action method="addJs"><script>scriptaculous/dragdrop.js</script></action> <action method="addJs"><script>scriptaculous/controls.js</script></action> <action method="addJs"><script>scriptaculous/slider.js</script></action> <action method="addJs"><script>varien/js.js</script></action> <action method="addJs"><script>varien/form.js</script></action> <action method="addJs"><script>varien/menu.js</script></action> <action method="addJs"><script>mage/translate.js</script></action> <action method="addJs"><script>mage/cookies.js</script></action> <block type="page/js_cookie" name="js_cookies" template="page/js/cookie.phtml"/> <action method="addCss"><stylesheet>css/styles.css</stylesheet></action> <action method="addItem"><type>skin_css</type><name>css/styles-ie.css</name><params/><if>lt IE 8</if></action> <action method="addCss"><stylesheet>css/widgets.css</stylesheet></action> <action method="addCss"><stylesheet>css/print.css</stylesheet><params>media="print"</params></action> <action method="addItem"><type>js</type><name>lib/ds-sleight.js</name><params/><if>lt IE 7</if></action> <action method="addItem"><type>skin_js</type><name>js/ie6.js</name><params/><if>lt IE 7</if></action> </block> … |
For headers to be displayed, the block Mage_Page_Block_Html_Head is created and addJs, addCss, addItem methods are called. addJs, addCss methods call addItem:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public function addCss($name, $params = "") { $this->addItem('skin_css', $name, $params); return $this; } public function addJs($name, $params = "") { $this->addItem('js', $name, $params); return $this; } public function addItem($type, $name, $params=null, $if=null, $cond=null) { if ($type==='skin_css' && empty($params)) { $params = 'media="all"'; } $this->_data['items'][$type.'/'.$name] = array( 'type' => $type, 'name' => $name, 'params' => $params, 'if' => $if, 'cond' => $cond, ); return $this; } |
Depending on the settings dev / js / merge_files, dev / css / merge_css_files CSS and JavaScript can be output separately or combined. If the setting dev / js / merge_files (System-> Configuration-> ADVANCED (Developer) Merge JavaScript Files) is enabled, all JavaScript files will be combined into one single file.
1 2 3 4 5 |
$html .= $this->_prepareStaticAndSkinElements('<script type="text/javascript" src="%s"%s></script>' . "\n", empty($items['js']) ? array() : $items['js'], empty($items['skin_js']) ? array() : $items['skin_js'], $shouldMergeJs ? array(Mage::getDesign(), 'getMergedJsUrl') : null ); |
_prepareStaticAndSkinElements method determines the correct path and prepares the HTML output by the template passed by the first parameter. Each JavaScript file can get not only a set of <script> inserts returned, but also a single one that includes the path to the merged file, for determining of which Mage_Core_Model_Design_Package::getMergedJsUrl() method is called:
1 2 3 4 5 6 7 8 9 10 11 12 |
public function getMergedJsUrl($files) { $targetFilename = md5(implode(',', $files)) . '.js'; $targetDir = $this->_initMergerDir('js'); if (!$targetDir) { return ''; } if ($this->_mergeFiles($files, $targetDir . DS . $targetFilename, false, null, 'js')) { return Mage::getBaseUrl('media', Mage::app()->getRequest()->isSecure()) . 'js/' . $targetFilename; } return ''; } |
Having determined the name of the future file, let us move to the process of creating this file. Mage_Core_Model_Design_Package::_mergeFiles() method synchronizes files with the database if the corresponding option is enabled, and calls Mage::helper (‘core’) ->mergeFiles().In case the file has already been created this method determines the need to update by comparing the edit time of the source files and the created one. If the update is needed the method filters files by their extensions, loads, merges the contents of the source files and stores the content in the merged file. Similarly for the CSS files, when dev/css/merge_css_files (System->Configuration->ADVANCED (Developer) Merge CSS Files) setting is enabled all CSS files are merged into one shared CSS file.
1 2 3 4 5 |
$html .= $this->_prepareStaticAndSkinElements('<link rel="stylesheet" type="text/css" href="%s"%s />' . "\n", empty($items['js_css']) ? array() : $items['js_css'], empty($items['skin_css']) ? array() : $items['skin_css'], $shouldMergeCss ? array(Mage::getDesign(), 'getMergedCssUrl') : null ); |
In this case, Mage_Core_Model_Design_Package::getMergedCssUrl() method is run instead of the method getMergedJsUrl. Besides the names of included files, host and port are also taken into account to form the file name.
1 2 3 4 5 6 7 8 9 |
… $targetFilename = md5(implode(',', $files) . "|{$hostname}|{$port}") . '.css'; $mergeFilesResult = $this->_mergeFiles( $files, $targetDir . DS . $targetFilename, false, array($this, 'beforeMergeCss'), 'css' ); … |
As in the case of JavaScript, the method Mage_Core_Model_Design_Package::mergeFiles is called, into which the callback function Mage_Core_Model_Design_Package::beforeMergeCss() is passed by the third parameter to adjust the file paths in CSS (used in constructions @import and url()).
Magento Custom Development
Take your online store to the next level with BelVG Magento Custom Development
Visit the page
Hello Jay!
You forgot about multi-store functionality. Every store has unique theme (skin). But filenames in all skins are the same. Only package or theme name differs. So if you use only filenames as hash you’ll rewrite existing merged file that belongs to another store. So with use of hostname and port you create unique filename for each store. We can compare host and port parts with salt in the password hashing procedure.
Hi,
Thanks for your post. Quick question!?
Why are the host and port taken into account when forming the merged CSS filename?
Cheers,
Jay