Php 在默认视图之前加载客户端视图(如果存在)

Php 在默认视图之前加载客户端视图(如果存在),php,zend-framework2,Php,Zend Framework2,在我的项目(BtoB项目)中,我有一个包含许多模块的全局应用程序。 每个模块为我的所有客户机提供通用功能 我在根目录中还有一个clients文件夹,在这个文件夹中,我有所有客户的特殊性,在他们的文件夹中。 这些文件夹不是模块。因此,它们没有加载Zf2。我通常用抽象工厂来加载这些特性 以下是我目前拥有的架构: - clients - clientOne - Invoice - Cart - Orders -

在我的项目(BtoB项目)中,我有一个包含许多模块的全局应用程序。 每个模块为我的所有客户机提供通用功能

我在根目录中还有一个clients文件夹,在这个文件夹中,我有所有客户的特殊性,在他们的文件夹中。 这些文件夹不是模块。因此,它们没有加载Zf2。我通常用抽象工厂来加载这些特性

以下是我目前拥有的架构:

-   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;
}