I'm working on a multi module site using Zend Framework 3 for ~6 months, learning as I go along. For the most part I have been pretty successful but I have run across a problem that I will describe below. There are ~20 modules and I have determined that in each modules module.config.php I should define a unique name for the Layout like this.
- Namespace Sample
- Controller Name: SampleController.php
- module/Sample/view/sam_layout.phtml
And in the Sample modules module.config.php
'view_manager' => [
'display_not_found_reason' => true,
'display_exceptions' => true,
'doctype' => 'HTML5',
'template_map' => [
'layout/layout' => _DIR__ . '/../view/layout/sam_layout.phtml',
'sample/sample/index' => __DIR__ . '/../view/sample/sample/index.phtml',
],
'template_path_stack' => [
'Sample' => __DIR__ . '/../view',
If I call the layout file 'layout.phtml' even though I'm mindful of Namespaces it is not certain that the intended view appears or is only partially correct. I noticed if I rearrange the module names in composer.json and modules.config.php then run composer dump-autoload I get a different set of mixed up layouts regardless of if I give the layout.phtml file a unique prefix or not.
The only thing that appears to clear this up is to do something like clear out the composer.json and modules.config.php except for the Application module then re-run composer dump-autoload or change the modules name add it to the composer.json & modules.config re-run dump-autoload and then put the module name back.. Essentially shaking up the config in order to force what ever seems to be keeping the views scrambled to let go and re-map everything. Note that when this problem magically clears up it is gone and we can continue developing code as if nothing was ever wrong.
I should note we start with the skeleton-application in development mode. I actually delete any files in the data/cache folder. Most of the views in the different modules share the Nav Bar but multiple modules could pick a particular modules Nav Bar view and it is very difficult to clear out.
So having described the above I have some questions. Do other developers have this same problem why can't I give a layout script a unique name and expect the view to be honored? As we develop further I'm sure each module will have more layouts and of course more .phtml files in the sample/sample folder (Taking the example from above) will we continue to see this behavior? Could the Apache Web Server or PHP7 be caching files and paths causing this problem and ZF3 has nothing to do with it? Note: I don't have opcache enabled.
Please be merciful: if you bury me in OO code it likely won't help me understand what is happening.
This is legitimate question if you're new to ZF2/3.
One thing you should know is that all configuration (application.config.php
, modules.config.php
and every modules module.config.php
and any other config file you define should be included e.g. in Module.php
class), they all get merged into a single config array.
As such, if you have the following bit in every one of your modules, only the last one will be valid if you merge using array_merge_recursive
and only the first if you use array_merge
.
'view_manager' => [
// ... others
'template_map' => [
'layout/layout' => _DIR__ . '/../view/layout/sam_layout.phtml',
// ... others
],
],
As such, make sure to either:
- Define associative array keys only once!
- Make sure you're overwriting the value in an extending module for a reason!
Personally, I use both of these rules. Define something once and overwrite when required. However, be careful with the latter, it's very easy to make a mistake.
One such mistake is to define layout/layout
in your Application
module and define it again in your User
module and again in another. Don't do that, ever ;)
First fix your config issue
To give you a bit of a cleaned up config, so that you don't lose your way, use an AbstractModule.php
class. I've copied mine from someone's snippet (cannot recall who from, otherwise would credit).
namespace Your\Core\Or\Mvc\Module\Namespace;
use Zend\ModuleManager\Feature\AutoloaderProviderInterface;
use Zend\ModuleManager\Feature\ConfigProviderInterface;
/**
* Class AbstractModule
* @package Your\Core\Or\Mvc\Module\Namespace
*/
abstract class AbstractModule implements ConfigProviderInterface, AutoloaderProviderInterface
{
/**
* @var String Path of current module
*/
protected $path;
/**
* @var String Namespace of current module
*/
protected $namespace;
/**
* This is to be called by descendant classes with:
* parent::__construct(__DIR__, __NAMESPACE__)
*
* @param $path string Module path
* @param $namespace string Module namespace
*/
public function __construct($path, $namespace)
{
$this->path = $path;
$this->namespace = $namespace;
}
/**
* @return array
*/
public function getConfig()
{
$config = [];
foreach (glob($this->path . '/config/*.php') as $filename) {
$config = array_merge_recursive($config, include $filename);
}
return $config;
}
/**
* @return array
*/
public function getAutoloaderConfig()
{
return [
'Zend\Loader\StandardAutoloader' => [
'namespaces' => [
$this->namespace => $this->path . DIRECTORY_SEPARATOR . 'src',
],
],
];
}
}
Use in each of your modules the following Module.php
namespace Your\Module\Namespace;
use Your\Core\Or\Mvc\Module\Namespace\AbstractModule;
/**
* Class Module
* @package Your\Module\Namespace
*/
class Module extends AbstractModule
{
/**
* Module constructor.
*/
public function __construct()
{
parent::__construct(__DIR__, __NAMESPACE__);
}
}
Why would I use all of this OO muck?
Well, if you have this code, you can separate all of your config files, in your modules, by subject. As such, in a APP_DIR/module/MODULE_NAME/config/
folder you could now put a bunch of config files, such as:
- module.config.php
- routes.config.php
- custom.config.php
And all of them would be loaded.
Mind: this does not address the issue you had of layouts being overwritten by others. This is due to using the same config key name (layout/layout
) for all of your config.
Second set up config to allow for different layouts
Next, you want to use different layouts per module. This is no problem, however, you need to make sure you allow for configuration that sets it. As such, we use a little snippet of code.
If you have a module specifically for your Themes, add this function to its Module.php
. Else, you might want to add this to the Module.php
class in the Application
module.
/**
* @param MvcEvent $e
*/
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
/**
* Source: https://github.com/Hounddog/HdRouteLayouts/blob/master/Module.php
* Add below AND route_layouts => [ %route% => %template/layout% ] to a module to allow route based layout
*
* Below example applies layout in [layout/admin => [ %path/to/layout.phtml% ] to all routes starting with
* "admin*" as defined in the "route_layouts => []" array.
*
* 'view_manager' => [
* 'template_map' => [
* 'layout/admin' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'view'
* . DIRECTORY_SEPARATOR . 'layout' . DIRECTORY_SEPARATOR . 'layout.phtml',
* ],
* ],
*
* 'route_layouts' => [
* 'admin*' => 'layout/admin',
* ],
*/
$e->getApplication()->getEventManager()->getSharedManager()
->attach(AbstractActionController::class, MvcEvent::EVENT_DISPATCH, function (MvcEvent $e) {
$controller = $e->getTarget();
$routeName = $e->getRouteMatch()->getMatchedRouteName();
$config = $e->getApplication()->getServiceManager()->get('config');
$layoutConfig = isset($config['route_layouts']) ? $config['route_layouts'] : [];
if (isset($layoutConfig) && count($layoutConfig) > 0) {
if (isset($layoutConfig[$routeName])) {
$controller->layout($layoutConfig[$routeName]);
} else {
$rules = array_keys($layoutConfig);
foreach ($rules as $routeRule) {
if (fnmatch($routeRule, $routeName, FNM_CASEFOLD)) {
$controller->layout($layoutConfig[$routeRule]);
break;
}
}
}
}
}, 100);
}
We can now add specific route layouts!
We add new layouts by registering them in the configuration. From one of my projects, we have a few layouts added, here's the config added to use the different layouts, based on routes.
// 'route_layouts' is a new "top-level" config array key
// Here you define: route -> template_name
'route_layouts' => [
'*' => 'layout/layout',
'login' => 'layout/login',
'register' => 'layout/login',
'error*' => 'error/error',
'error/404' => 'error/404',
'error/index' => 'error/index',
],
'view_manager' => [
'template_map' => [
// Here you define: template_name -> location
'layout/layout' => __DIR__ . '/../view/layout/layout.phtml',
'layout/login' => __DIR__ . '/../view/layout/login.phtml',
'layout/register' => __DIR__ . '/../view/layout/register.phtml',
'error/error' => __DIR__ . '/../view/error/error.phtml',
'error/404' => __DIR__ . '/../view/error/404.phtml',
'error/index' => __DIR__ . '/../view/error/index.phtml',
],
// ... other config
],
Hopefully this clears up a lot for you concerning your issues with configuration and layouts. Feel free to comment with a related question if you cannot get it working with these examples.
来源:https://stackoverflow.com/questions/48217573/zf3-multiple-modules-and-multiple-layouts