Php 在默认视图之前加载客户端视图(如果存在)
在我的项目(BtoB项目)中,我有一个包含许多模块的全局应用程序。 每个模块为我的所有客户机提供通用功能 我在根目录中还有一个clients文件夹,在这个文件夹中,我有所有客户的特殊性,在他们的文件夹中。 这些文件夹不是模块。因此,它们没有加载Zf2。我通常用抽象工厂来加载这些特性 以下是我目前拥有的架构:Php 在默认视图之前加载客户端视图(如果存在),php,zend-framework2,Php,Zend Framework2,在我的项目(BtoB项目)中,我有一个包含许多模块的全局应用程序。 每个模块为我的所有客户机提供通用功能 我在根目录中还有一个clients文件夹,在这个文件夹中,我有所有客户的特殊性,在他们的文件夹中。 这些文件夹不是模块。因此,它们没有加载Zf2。我通常用抽象工厂来加载这些特性 以下是我目前拥有的架构: - clients - clientOne - Invoice - Cart - Orders -
- 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)之后,我尝试实现这个解决方案:
所以我有一个客户策略;它的工厂是这样的:
<?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;
}
}
因此,当我的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;
}
}
不要把事情过分复杂化。只需在渲染之前设置ViewModel的模板
$vm = new ViewModel();
$vm->setTemplate( $user_service->getTemplate( $this->getRequest() ) );
return $vm;
如果您将您的用户注入到这个虚构的用户服务中,并使用它确定要注入哪个模板,则非常干净
$user_服务的关注点应该与控制器操作的关注点完全不同。如果您真的想这样做(我不确定这是否是最好的方法),那么您可以使用自定义逻辑扩展
TemplateMapResolver
,并在渲染器
实例中进行设置
创建自定义类:
<?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];
}
}
您可能仍然需要注意在解析器中设置映射。也许你可以从旧的解析器那里得到它?我不确定。。。那是你自己去发现的。这只是为了让你走上正确的道路
因此,如果您将cart\u视图设置为模板,它将首先尝试获取client\u name\u cart\u视图
,如果未找到,它将设置cart\u视图
更新
如果您想将其提升到下一个级别,那么您可以做的是创建一个自定义视图模型,例如扩展普通ViewModel
类的ClientViewModel
。
此ClientViewModel
的构造函数采用客户端和模板名称:
new ClientViewModel($client, $template, $variables, $options);
$variables
和$options
是可选的,可以传递给父对象::\u构造
(普通视图模型的构造函数
)
下一步是创建应用程序\View\ClientStrategy
此策略在渲染事件上连接,在此策略中,您可以使用自定义的TemplateMapResolver
集添加一个ViewRenderer
实例。在渲染过程中,您可以从ViewModel
获取客户端,并使用此客户端在TemplateMapResolver
中找到正确的模板
更多细节可以在网上找到,这里有一些例子。例如,检查
这样做的好处是,具有ViewModel
或JsonModel
的其他视图将按正常方式呈现,只有ClientViewModel
得到特殊处理。因此,您没有破坏应用程序的默认逻辑。要求
- 每个客户端有多个可能的视图
- 如果未找到特定于客户端的视图,则默认视图回退
创建一个新的服务,比如说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”项下注册,但是控制器中模板的名称从未更改
编辑
您可以只使用一个工厂并从配置中提取(参见上面的更改)
看来我解决了我的问题,但我不确定这是不是一个好办法。因此,如果有人能做得更好,我就让悬赏金去寻找更好的解决方案,如果有的话
以下是我所做的:
/**
* 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,这是用于加载自定义视图的。但最重要的是:我不创建新的冲突解决程序,我只是通过以下行将我的冲突解决程序添加到列表中:
$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();
}
我的解决方案是,强制我在我的客户文件夹中创建一个与文件夹同名的类,因此,如果我的客户名称是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\u path\u stack
或my others路由的情况下执行此操作是可能的。现在我必须在控制器中调用setTemplate
方法,如下所示:
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();
}
}
// code ...
public function cartAction() {
$view->setTemplate('cartView');
return $view;
}
ZendFramework将首先检查my clients文件夹中是否存在自定义视图,如果找不到视图,则加载公共视图
感谢@Wilt和@AlexP的贡献和帮助。查看Zend\View template\u path\u堆栈。但是,您可能希望明确视图/模板映射。感谢您的提示,我刚刚看到我可能希望使用Resolver而不是ViewStrategy抱歉,就是这样,如果我有70个客户端,我就不能使用用户服务。我需要做得很好,你想怎么做就怎么做,这是正确的方法。无论你有5个客户还是500个。决定客户端模板工具包的逻辑应该被封装以保持可测试的行为。我认为使用新的ViewStrategy和解析器可以实现这一点。因为这是这一层的目的。有时事情会过分复杂
/**
* 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',
);
}
// code ...
public function cartAction() {
$view->setTemplate('cartView');
return $view;
}