UPDATED AT 20160310
Conclusion
It is always set either via updateTheme()
or from the collection (via DB) if your appState->getMode() == AppState::MODE_PRODUCTION
Answer
To answer the question What is the way to get Magento to reload the theme.xml file the answer is:
Set the application state to developer
using SetEnv MAGE_MODE developer
in .htaccess
(or nginx equivalent) and then log in to the admin area (or refresh any admin route) to trigger Magento\Theme\Model\Theme\Plugin\Registration::beforeDispatch()
.
The theme table in the database is updated due to
\\Magento\Theme\Model\Theme\Plugin\Registration::updateThemeData()
\\...
$themeData->setParentId($parentTheme->getId());`.
\\...
See analysis below for details.
Analysis
Wow the Magento 2 code seems really complex to me. Have you studied this function beforeDispatch()
which calls updateThemeData()
but only if ($this->appState->getMode() != AppState::MODE_PRODUCTION)
//namespace: namespace Magento\Theme\Model\Theme\Plugin;
//class: Registration
//file: app/code/Magento/Theme/Model/Theme/Plugin/Registration.php
/**
* Add new theme from filesystem and update existing
*
* @param AbstractAction $subject
* @param RequestInterface $request
*
* @return void
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function beforeDispatch(
AbstractAction $subject,
RequestInterface $request
) {
try {
if ($this->appState->getMode() != AppState::MODE_PRODUCTION) {
$this->themeRegistration->register();
$this->updateThemeData();
}
} catch (LocalizedException $e) {
$this->logger->critical($e);
}
}
Probably you have been through this code.
beforeDispatch()
is called only via admin routes and not on front-end routes. Here is a trace:
#0 [internal function]: Magento\Theme\Model\Theme\Plugin\Registration->beforeDispatch(Object(Magento\Backend\Controller\Adminhtml\Dashboard\Index\Interceptor), Object(Magento\Framework\App\Request\Http))
#1 \magento2\lib\internal\Magento\Framework\Interception\Interceptor.php(122): call_user_func_array(Array, Array)
#2 \magento2\var\generation\Magento\Backend\Controller\Adminhtml\Dashboard\Index\Interceptor.php(39): Magento\Backend\Controller\Adminhtml\Dashboard\Index\Interceptor->___callPlugins('dispatch', Array, Array)
#3 \magento2\lib\internal\Magento\Framework\App\FrontController.php(55): Magento\Backend\Controller\Adminhtml\Dashboard\Index\Interceptor->dispatch(Object(Magento\Framework\App\Request\Http))
#4 [internal function]: Magento\Framework\App\FrontController->dispatch(Object(Magento\Framework\App\Request\Http))
#5 \magento2\lib\internal\Magento\Framework\Interception\Interceptor.php(74): call_user_func_array(Array, Array)
#6 \magento2\lib\internal\Magento\Framework\Interception\Chain\Chain.php(70): Magento\Framework\App\FrontController\Interceptor->___callParent('dispatch', Array)
#7 \magento2\lib\internal\Magento\Framework\Interception\Interceptor.php(136): Magento\Framework\Interception\Chain\Chain->invokeNext('Magento\\Framewo...', 'dispatch', Object(Magento\Framework\App\FrontController\Interceptor), Array, 'install')
#8 \magento2\lib\internal\Magento\Framework\Module\Plugin\DbStatusValidator.php(69): Magento\Framework\App\FrontController\Interceptor->Magento\Framework\Interception\{closure}(Object(Magento\Framework\App\Request\Http))
#9 [internal function]: Magento\Framework\Module\Plugin\DbStatusValidator->aroundDispatch(Object(Magento\Framework\App\FrontController\Interceptor), Object(Closure), Object(Magento\Framework\App\Request\Http))
#10 \magento2\lib\internal\Magento\Framework\Interception\Interceptor.php(141): call_user_func_array(Array, Array)
#11 \magento2\var\generation\Magento\Framework\App\FrontController\Interceptor.php(26): Magento\Framework\App\FrontController\Interceptor->___callPlugins('dispatch', Array, Array)
#12 \magento2\lib\internal\Magento\Framework\App\Http.php(115): Magento\Framework\App\FrontController\Interceptor->dispatch(Object(Magento\Framework\App\Request\Http))
#13 \magento2\lib\internal\Magento\Framework\App\Bootstrap.php(258): Magento\Framework\App\Http->launch()
#14 \magento2\index.php(39): Magento\Framework\App\Bootstrap->run(Object(Magento\Framework\App\Http))
Actually I see beforeDispatch()
calls updateThemeData()
which contains this nugget:
//namespace: namespace Magento\Theme\Model\Theme\Plugin;
//class: Registration
//file: app/code/Magento/Theme/Model/Theme/Plugin/Registration.php
//function: updateThemeData()
//...
if ($themeData->getParentTheme()) {
$parentTheme = $this->themeLoader->getThemeByFullPath(
$themeData->getParentTheme()->getFullPath()
);
$themeData->setParentId($parentTheme->getId());
}
//...
Which seems to actually (finally) refer to a config XML path $themeData->getParentTheme()->getFullPath()
but that function still uses $themeData->getParentTheme()
. Oh I think the logic is 'If I am updating a registered theme that has a parentId in the collection (via the DB) then seek a parent path in the config and update the collection'. So maybe this is it.
Otherwise I am at a complete loss as to how Magento\Theme\Model\Theme::getParentTheme()
implements getParentId()
which is declared in the theme interface. Surely it isn't magic. As you say it needs to come either from the DB via the collection or from the theme's config XML path (if it changed or isn't defined yet) but I can't find a definition of getParentId()
. Maybe it is always set via updateTheme()
OR from the collection (via DB) so too bad if your appState->getMode() == AppState::MODE_PRODUCTION
.
I found it useful to glean information from within updateThemeData()
by adding some log output:
//namespace: namespace Magento\Theme\Model\Theme\Plugin;
//class: Registration
//file: app/code/Magento/Theme/Model/Theme/Plugin/Registration.php
//function: updateThemeData()
//...
if ($themeData->getParentTheme()) {
$parentTheme = $this->themeLoader->getThemeByFullPath(
$themeData->getParentTheme()->getFullPath()
);
$this->logger->addDebug("Theme parent full path ".$themeData->getParentTheme()->getFullPath());
$this->logger->addDebug("Theme parent new ID ".$parentTheme->getId()); $themeData->setParentId($parentTheme->getId());
}
//...
Which will log to /var/log/debug.log
. With application state set to developer
I can see the parent ID is always set on every admin page refresh whether it was changed in theme.xml
or not. With application state production
the function is never run so I conclude:
It is always set via updateTheme()
OR from the collection (via DB) so too bad if your appState->getMode() == AppState::MODE_PRODUCTION
I think you probably are all in developer
app state. default
app state will trigger updateThemeData()
too of course. In further debugging I logged the theme full path for the parent theme of Luma which was frontend/Magento/blank
. The capital M
surprised me so maybe something to watch out for.