在我的项目(BtoB项目)中,我有一个包含大量模块的全局应用程序。 每个模块为我的所有客户提供共同的功能。
我还在根目录中有一个客户端文件夹,在其中,我在其文件夹中具有所有客户端特性。 文件夹,不是模块。所以他们没有加载Zf2。我通常用abstractFactories加载这些特性。
这个架构遵循我目前所拥有的:
- clients
- clientOne
- Invoice
- Cart
- Orders
- clientTwo
- Invoice
- Orders
- clientThree
- Reporting
- module
- Application
- CartModule
- InvoiceModule
- OrdersModule
- Reporting
我的客户希望拥有一些自定义视图,有时他们会要求我们提供这些视图。但我的应用程序为所有这些提供了一个共同的视图。我必须修改此体系结构以加载客户端视图(如果存在),或加载公共视图。
要处理这种情况,我想想进入每个客户端文件夹:
- client
- clientOne
- Invoice
- Cart
- View
- cartView.phtml
- Orders
编辑:
经过一些好的答案(@AlexP& @Wilt),我试图实现这个解决方案:
所以我有一个ClientStrategy;它的工厂就像这样:
<?php
namespace Application\View\Strategy;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Application\View\Resolver\TemplateMapResolver;
use Zend\View\Resolver;
class ClientStrategyFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$viewRenderer = $serviceLocator->get('ViewRenderer');
$session = new \Zend\Session\Container('Session');
$map = $serviceLocator->get('config')['view_manager']['template_map'];
$resolver = new Resolver\AggregateResolver();
$map = new TemplateMapResolver($map, $this->clientMap($session->offsetGet('cod_entprim')));
$resolver
->attach($map)
->attach(new Resolver\RelativeFallbackResolver($map));
$viewRenderer->setResolver($resolver);
return new ClientStrategy($viewRenderer);
}
/**
* permet de retourner le namespace du client selectionné avec la relation codpriml / nom de dossier
* @return array
*/
public function clientMap($codprim)
{
$clients = array(
21500 => 'clientOne',
32000 => 'clientTwo',
// ..
);
return (isset($clients[$codprim])) ? $clients[$codprim]: false;
}
}
我的clientMap方法允许我加载我的客户端文件夹,以及它可能包含的视图:
class ClientOne
{
/**
* get The main Code
* @return integer
*/
public function getCodEntPrim()
{
return 21500;
}
/**
* Load all customs views
* @return array
*/
public function customViews()
{
return array(
'addDotations' => __DIR__ . '/Dotations/view/dotations/dotations/add-dotations.phtml',
);
}
/**
* GetName
* @return string
*/
public function getName()
{
return get_class();
}
}
因此,当我的TemplateMapResolver完成他的工作时,我这样做:
<?php
namespace Application\View\Resolver;
class TemplateMapResolver extends \Zend\View\Resolver\TemplateMapResolver
{
/**
* Client name to use when retrieving view.
*
* @param string $clientName
*/
protected $clientName;
/**
* Merge nos vues avec celle clients avant de repeupler l'arrayMap global
* @param array $map [description]
*/
public function __construct(array $map, $client)
{
$this->setClientName($client);
if ($this->getCLientName()) {
$map = $this->mergeMap($map);
}
parent::__construct($map);
}
/**
* Merge les map normales avec les map clients, pas propre ?
* @param array $map
* @return array
*/
public function mergeMap($map)
{
$name = $this->getClientName() . '\\' . $this->getClientName() ;
$class = new $name;
$clientMap = $class->customViews();
return array_replace_recursive($map, $clientMap);
}
/**
* Retrieve a template path by name
*
* @param string $name
* @return false|string
* @throws Exception\DomainException if no entry exists
*/
public function get($name)
{
return parent::get($name);
}
/**
* Gets the Client name to use when retrieving view.
*
* @return string
*/
public function getClientName()
{
return $this->clientName;
}
/**
* Sets the Client name to use when retrieving view.
*
* @param mixed $clientName the client name
*
* @return self
*/
public function setClientName($clientName)
{
$this->clientName = $clientName;
return $this;
}
}
我尝试了很多东西,这很有效,但有些问题出现了:
- 我的template_path_stack不再有用了,所以我的很多观点都被打破了。
- 我认为这是一个完全混乱,这样做。
- 难以维持。
- 我理解得更好,它是如何运作的,但我仍然无法以良好的方式实现它。
答案 0 :(得分:2)
如果你真的想这样做(我不确定它是否是最佳方式),那么你可以使用自定义逻辑扩展TemplateMapResolver
并将其设置在Renderer
实例中。< / p>
制作自定义课程:
<?php
Application\View\Resolver
class TemplateMapResolver extends \Zend\View\Resolver\TemplateMapResolver
{
/**
* Client name to use when retrieving template.
*
* @param string $clientName
*/
protected $clientName;
/**
* Retrieve a template path by name
*
* @param string $name
* @return false|string
* @throws Exception\DomainException if no entry exists
*/
public function get($name)
{
if ($this->has($clientName . '_' . $name)) {
return $this->map[$clientName . '_' . $name];
}
if (!$this->has($name)) {
return false;
}
return $this->map[$name];
}
}
现在就像:
$resolver = new TemplateMapResolver();
$resolver->setClientName($clientName);
// Get the renderer instance
$renderer->setResolver($resolver);
您可能仍需要在解析器中设置地图。也许你可以从旧的解析器中得到它?我不确定......那是给你找的。这只是为了让你走上正确的道路。
因此,如果您将cart_view
设置为模板,则会首先尝试获取client_name_cart_view
如果未找到cart_view
。
如果您想将其提升到新的水平,那么您可以做的是制作一个自定义视图模型,例如ClientViewModel
扩展正常ViewModel
类。
此ClientViewModel
的构造函数同时包含客户端和模板名称:
new ClientViewModel($client, $template, $variables, $options);
$variables
和$options
是可选的,可以传递给parent::__construct
(普通ViewModel
的构造函数)
下一步是创建Application\View\ClientStrategy
。
此策略与呈现事件相关联,在此策略中,您可以使用自定义ViewRenderer
集添加TemplateMapResolver
实例。在渲染过程中,您可以从ViewModel
获取客户,并使用此客户端在TemplateMapResolver
中找到正确的模板。
更多细节可以在网上找到,有例子。检查例如here。
优势在于ViewModel
或JsonModel
的其他观看次数将正常呈现,只有您的ClientViewModel
获得特殊待遇。因此,您不会破坏应用程序的默认逻辑。
答案 1 :(得分:2)
要求
创建一个新服务,比如TemplateProviderService
,它有一个简单的界面。
interface ViewTemplateProviderInterface
{
public function hasTemplate($name);
public function getTemplates();
public function setTemplates($templates);
public function getTemplate($name);
public function setTemplate($name, $template);
public function removeTemplate($name);
public function removeTemplates();
}
在控制器类中注入和硬编码模板名称。
// Some controller class
public function fooAction()
{
$view = new ViewModel();
$view->setTemplate($this->templateProvider->get('some_view_name'));
return $view;
}
现在,您可以创建特定于客户端的工厂,将自定义模板脚本配置注入模板提供程序。您需要做的就是决定要将哪个模板提供程序服务注入控制器。
class ViewTemplateProviderFactory
{
public function __invoke($sm, $name, $rname)
{
$config = $sm->get('config');
if (! isset($config['view_template_providers'][$rname])) {
throw new ServiceNotCreatedException(sprintf('No view template provider config for \'%s\'.', $rname));
}
return new ViewTemplateProvider($config['view_template_providers'][$rname]);
}
}
此处的关键是所有客户端的所有视图脚本都正常注册在“view_manager”键下,但 controller 中模板的名称永远不会更改。
修改强>
您可以使用一个工厂并从配置中提取(请参阅上面的更改)。
return [
'view_template_providers' => [
'ClientOneTemplateProvider' => [
'some_view_name' => 'name_of_script_1'
],
'ClientTwoTemplateProvider' => [
'some_view_name' => 'name_of_script_2'
],
'ClientThreeTemplateProvider' => [
'some_view_name' => 'name_of_script_3',
],
],
'service_manager' => [
'factories' => [
'ClientOneTemplateProvider' => 'ViewTemplateProviderFactory',
'ClientTwoTemplateProvider' => 'ViewTemplateProviderFactory',
'ClientThreeTemplateProvider' => 'ViewTemplateProviderFactory',
],
],
'view_manager' => [
'template_map' => [
'name_of_script_1' => __DIR__ . 'file/path/to/script',
'name_of_script_2' => __DIR__ . 'file/path/to/script',
'name_of_script_3' => __DIR__ . 'file/path/to/script',
],
],
];
答案 2 :(得分:1)
似乎我解决了我的问题,但我不确定这是做到这一点的好方法。因此,如果有人能做得更好,我会让赏金运行以获得更好的解决方案,如果存在的话。
以下是我所做的:
/**
* Factory permettant d'établir que les vues client soient chargé si elle existent, avant les vues par défaut.
*/
class ClientStrategyFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$viewRenderer = $serviceLocator->get('ViewRenderer');
$session = new \Zend\Session\Container('Session');
$clientList = $serviceLocator->get('Config')['customers_list'];
$clientName = $this->clientMap($session->offsetGet('cod_entprim'), $clientList);
$clientMap = new TemplateMapResolver($clientName);
$viewRenderer->resolver()->getIterator()->insert($clientMap, 2);
return new ClientStrategy($viewRenderer);
}
/**
* permet de retourner le namespace du client selectionné avec la relation codpriml / nom de dossier
* @param integer $codprim
* @param array $clientList
* @return array
*/
public function clientMap($codprim, $clientList)
{
return (isset($clientList[$codprim])) ? $clientList[$codprim]: false;
}
}
您可以看到我的自定义TemplateMapResolver需要一个clientName,这是用于加载自定义视图。但最重要的是:我没有创建一个新的Resolver,我只是通过这一行将我的Resolver添加到列表中:
$viewRenderer->resolver()->getIterator()->insert($clientMap, 2);
第二个参数表示该解析器是最高优先级(默认优先级为1)
我的TemplateMapResolver非常简单,最重要的是:
public function __construct($client)
{
$this->setClientName($client);
if ($this->getCLientName()) {
$map = $this->getMap();
} else {
$map = array();
}
parent::__construct($map);
}
/**
* Return all custom views for one client
* @param array $map
* @return array
*/
public function getMap()
{
$name = $this->getClientName() . '\\' . $this->getClientName() ;
$class = new $name;
return $class->customViews();
}
我的解决方案,强迫我在我的客户端文件夹中创建一个具有相同文件夹名称的类,因此,如果我的clientName是TrumanShow,我将拥有如下架构:
- [clients]
-- [TrumanShow]
--- TrumanShow.php
--- [Cart]
---- [view]
----- [cart]
------ [index]
------- cart-view.phtml
--- [Invoice]
--- [Reporting]
在这个文件中,我将使用此函数声明我的所有自定义视图:
/**
* Ici nous mettons nos custom views afin de les charger dans le template Map
* @return array
*/
public function customViews()
{
return array(
'cartView' => __DIR__ . '/Cart/view/cart/index/cart-view.phtml',
);
}
因此可以在不中断template_path_stack
或其他路线的情况下执行此操作。现在我必须在我的控制器中调用setTemplate
方法,如下所示:
// code ...
public function cartAction() {
$view->setTemplate('cartView');
return $view;
}
ZendFramework将首先检查我的客户端文件夹中是否存在自定义视图,或者如果没有找到视图则加载公共视图。
感谢@Wilt和@AlexP的贡献和帮助。
答案 3 :(得分:-1)
不要过度复杂化。只需在渲染之前设置ViewModel的模板。
$vm = new ViewModel();
$vm->setTemplate( $user_service->getTemplate( $this->getRequest() ) );
return $vm;
如果您将用户注入这个虚构的用户服务,并使用它来确定要注入的模板,那就很干净了。
$ user_service的关注应该与您对Controller操作的关注完全不同。