Please, select any of the themes bellow and see the magic happens.
public\themes\THEME_NAME
LearnZF2Themes\themes\THEME_NAME
This is where all the magic happens. The system retrives the theme name from the Config
and searches inside themes config for a key with this theme name. After that it checks for its configuration from module.config.php and loads it.LearnZF2Themes\Factory\ThemesFactory
final class ThemesFactory
{
/**
* {@inheritdoc}
*/
public function __invoke(ServiceLocatorInterface $serviceLocator)
{
$themesConfig = $serviceLocator->get("getThemesFromDir");
$config = $serviceLocator->get("Config");
$headScript = $serviceLocator->get("ViewHelperManager")->get("HeadScript");
$headLink = $serviceLocator->get("ViewHelperManager")->get("headLink");
$publicDir = "/themes/".$config["theme"]["name"].DIRECTORY_SEPARATOR;
/*
* Get theme name from config and load it.
*
* At this point the user has already been selected the new theme he wants to use
* from indexAction.
*/
$viewTemplate = $serviceLocator->get("ViewTemplatePathStack");
$themes = $themesConfig["themes"][$config["theme"]["name"]];
if (isset($themes["template_path_stack"])) {
$viewTemplate->addPaths($themes["template_path_stack"]);
}
if (isset($themes["template_map"])) {
$viewTemplate = $serviceLocator->get("ViewTemplateMapResolver");
$viewTemplate->merge($themes["template_map"]);
}
foreach ($themes["css"] as $key => $file) {
$headLink->prependStylesheet($publicDir.$file);
}
foreach ($themes["js"] as $key => $file) {
$headScript->prependFile($publicDir.$file);
}
return $viewTemplate;
}
}
This factory LearnZF2Themes\Factory\GetThemesFromDir
will iterate over module\LearnZF2Themes\themes
and load every theme and its configuration in the global Config.
final class GetThemesFromDir
{
/**
* {@inheritdoc}
*/
public function __invoke()
{
$path = __DIR__."/../../../themes/";
$dir = new DirectoryIterator($path);
$themesConfig = [];
foreach ($dir as $file) {
if (!$file->isDot()) {
$hasConfig = $path.$file->getBasename()."/config/module.config.php";
if (is_file($hasConfig)) {
$themesConfig["themes"][$file->getBasename()] = include $hasConfig;
}
}
}
return $themesConfig;
}
}
After both factories have been executed we need to pass their configs into the IndexController, where the system will show all available themes and their information. This is done via a 3rd factory LearnZF2Themes\Factory\Controller\IndexControllerFactory
final class IndexControllerFactory
{
/**
* {@inheritdoc}
*/
public function __invoke(ControllerManager $controllerManager)
{
$serviceLocator = $controllerManager->getServiceLocator();
$themesConfig = $serviceLocator->get("getThemesFromDir");
$reloadService = $serviceLocator->get("reloadService");
$controller = new IndexController($themesConfig, $reloadService);
return $controller;
}
}
We need to attach two event listeners before the real rendering of the view files in LearnZF2Themes\Module.php
. The first listener listens for the render event, which renders the current them. The second listener is a custom listener, which is appended to the SharedEventManager and reloads the Config, after a different theme has been selected.
// ...code
/**
* @var \Zend\getServiceManager\ServiceManager
*/
private $service = null;
/**
* Listen to the bootstrap event.
*
* @param EventInterface $event
*/
public function onBootstrap(EventInterface $event)
{
$app = $event->getApplication();
$this->service = $app->getServiceManager();
$eventManager = $app->getEventManager();
$sharedEventManager = $app->getEventManager()->getSharedManager();
$eventManager->attach("render", [$this,"loadTheme"], 100);
$sharedEventManager->attach(ReloadService::class, "reload", [$this, "reloadConfig"], 100);
}
/**
* Listen for theme change and override Config.
*/
public function reloadConfig()
{
$request = $this->service->get("Request");
if ($request->isPost()) {
$config = $this->service->get("Config");
$themeName = $request->getPost()["themeName"];
$this->service->setAllowOverride(true);
$config["theme"]["name"] = $themeName;
$this->service->setService("Config", $config);
$this->service->setAllowOverride(false);
}
}
/**
* Setup theme.
*
* @param EventInterface $event
*/
public function loadTheme(EventInterface $event)
{
return $this->service->get("initThemes");
}
// ...code
LearnZF2Themes\Service\ReloadService
is a simple service that is being called inside the controller.
Its function is used to trigger the custom reload event listener, which calls reloadConfig inside Module.php and reloads the Config.
final class ReloadService implements EventManagerAwareInterface
{
use EventManagerAwareTrait;
public function reload()
{
$this->getEventManager()->trigger(__FUNCTION__, $this);
}
}
LearnZF2Themes\config\module.config.php
"controllers" => [
"factories" => [
"LearnZF2Themes\Controller\Index" => "LearnZF2Themes\Factory\Controller\IndexControllerFactory",
],
],
"service_manager" => [
"factories" => [
"initThemes" => "LearnZF2Themes\Factory\ThemesFactory",
"getThemesFromDir" => "LearnZF2Themes\Factory\GetThemesFromDir",
],
"invokables" => [
"reloadService" => "LearnZF2Themes\Service\ReloadService",
],
],
"theme" => [
"name" => "default",
],
The controller has only 1 action, which is needed to change the theme. LearnZF2Themes\Controller\IndexController
class IndexController extends AbstractActionController
{
/**
* @var array
*/
private $themesConfig = [];
/**
* @var mixed
*/
private $reloadService = null;
/**
* @method __construct
*
* @param array $themesConfig
* @param mixed $reloadService
*/
public function __construct(array $themesConfig = [], $reloadService)
{
$this->themesConfig = $themesConfig;
$this->reloadService = $reloadService;
}
/**
* This action shows the list of all themes.
*
* @method indexAction
*
* @return ViewModel
*/
public function indexAction()
{
$request = $this->getRequest();
if ($request->isPost()) {
$filename = __DIR__."/../../../config/module.config.php";
$settings = include $filename;
$themeName = $request->getPost()["themeName"];
$settings["theme"]["name"] = $themeName;
file_put_contents($filename, "<?php return ".var_export($settings, true).";");
$this->reloadService->reload();
}
return new ViewModel([
"themes" => $this->themesConfig,
]);
}
}
By the way, you can find other examples using Zend Framework 2 in our home page :)