inviqa/magento-symfony-container

为Magento提供Symfony DI容器的实例

安装次数: 36,214

依赖者: 0

建议者: 0

安全: 0

星标: 24

关注者: 16

分支: 9

公开问题: 1

类型:magento-module

1.3.0 2018-08-29 13:17 UTC

README

Scrutinizer Code Quality Build Status

magento-symfony-container

为Magento提供Symfony DI容器的实例

有关Symfony DI组件的文档可以在此处找到:https://symfony.ac.cn/doc/current/components/dependency_injection/index.html.

容器请求第一次时,会扫描配置目录并编译容器。容器将被缓存到public/var/cache/container.cache.php中,随后从那里读取。您可以使用管理员缓存控制面板来启用/禁用容器的缓存,当禁用时,所有服务配置文件在更改时都将被重新读取,否则将忽略。要强制刷新缓存,可以使用n98-magerun或管理员缓存控制面板。

服务配置

所有服务配置文件应位于系统范围的etc/目录中,或位于每个模块的etc/目录中。预期的格式是XML,因此配置文件应命名为"services.xml"。

以下是一个定义名为"acme.checkout"的服务的示例,该服务依赖于一个Magento目录模型和一个邮件服务。通过配置,我们可以提供"acme.product.catalog",它是通过调用Mage::getModel('inviqa_acme/catalog')构建的,作为"acme.checkout"的依赖项。这样,我们的"acme.checkout"服务/类就与Magento及其逻辑和业务规则解耦了,可以独立测试。

<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="https://symfony.ac.cn/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="https://symfony.ac.cn/schema/dic/services https://symfony.ac.cn/schema/dic/services/services-1.0.xsd">

    <services>
        
        <service id="acme.checkout" class="Inviqa\Acme\Checkout">
            <argument type="service" id="acme.product.catalog" />
            <argument type="service" id="acme.mailer" />
        </service>
        
        <service id="acme.product.catalog" class="Inviqa_Acme_Model_Catalog">
            <factory class="Mage" method="getModel"/>
            <argument>inviqa_acme/catalog</argument>
        </service>
        
        <service id="acme.mailer" class="Inviqa\Mailer">
        </service>
        
    </services>
</container>

Inviqa\Acme\Checkout的实现可能如下所示

namespace Inviqa\Acme;

use Inviqa\Acme\Catalog;
use Inviqa\Mailer;

class Checkout
{
    private $catalog;
    
    private $mailer;

    public function __construct(Catalog $catalog, Mailer $mailer)
    {
        $this->catalog = $catalog;
        $this->mailer = $mailer;
    }
    
    public function process()
    {
        // checkout processing rules
    }
}

以及一个目录接口

namespace Inviqa\Acme

interface Catalog
{
    public function process(Products $products = null);
    public function start();
}

并且让Inviqa_Acme_Model_Catalog实现目录接口。

用法

使用DI容器的想法是可以轻松解耦代码,因此直接访问容器是不良做法,应限制在绝对必要的地方,例如服务无法自动提供的地方(例如,一个Magento控制器或观察者)。

通过特性使用

class Inviqa_Acme_IndexController
{
    use Inviqa_SymfonyContainer_Helper_ServiceProvider;

    public function indexAction()
    {
        $this->getService('acme.checkout')->process();
        
        $this->loadLayout();
        $this->renderLayout();
    }
}

直接通过助手使用

$container = Mage::helper('inviqa_symfonyContainer/containerProvider')->getContainer();

测试环境

配置构建器读取Magento配置节点"global/environment",并使用它将容器生成器切换到"test"环境。在"global/environment"节点中预期的字符串是"test"。在这种模式下,容器生成器将读取额外的services_test.xml文件,如果它们的id匹配,将覆盖services.xml中定义的服务。这样,您可以在集成测试目的下使用"mock"服务。(见https://github.com/inviqa/symfony-container-generator中的附加文档)

向服务构造函数提供Magento商店配置值

如果您的服务需要从商店配置中获取值,例如通常需要调用Mage::getStoreConfig('web/secure/base_url'),您可以使用特殊标签mage.config替换您的服务定义中的节点。该"key"属性是常规的Magento商店配置密钥。这将为请求的商店配置值添加到服务构造函数参数列表中。

    <services>
        <service id="acme.service" class="Acme_Service">
            <argument type="service" id="some.service"/>
            <tag name="mage.config" key="payment/amazonpayments_cba/title"/>
            <tag name="mage.config" key="web/secure/base_url"/>
        </service>
    </services>

这将导致Acme_Service的构造函数被调用并带有两个额外的参数

class Acme_Service
{
    public function __construct(SomeSerivce $someService, $amazonPaymentsTitle, $secureBaseUrl)
    {
        $this->_secureBaseUrl = $secureBaseUrl; // will have value of e.g: https://my-magento.dev/  
    }
}

如果指定的键不存在或为空,则参数将为null或空。

通过__dependencies显式提供依赖项(自版本0.5.0以来)

某些 Magento 类,如控制器、观察者和块,由系统实例化,因此不能由 DI 容器实例化。尽管如此,额外的服务标签可以使其更容易显式地提供对这些类的依赖。将 mage.injectable 标签添加到服务中,将允许您在服务实例化后提供这些服务依赖项。如果调用 ServiceInjector::setupDependencies($class) 在服务实例化后,并将您的服务具有包含与参数类型匹配的类型提示的 __dependencies 方法,则服务参数将被提供给 __dependencies 方法。

为了方便起见,通过挂钩 pre-dispatch 事件,在控制器上调用 setupDependencies,对于观察者和块等其他类,您将必须通过覆盖类构造函数来自行调用它。

对于控制器,服务定义可能如下所示

        <service id="acme.product.catalog" class="Mage_Catalog_Model_Product">
            <factory class="Mage" method="getModel"/>
            <argument>catalog/product</argument>
        </service>

        <service id="acme.sales.order" class="Mage_Sales_Model_Order">
            <factory class="Mage" method="getModel"/>
            <argument>sales/order</argument>
        </service>

        <service id="controllers.acme" class="Acme_Shop_IndexController">
            <argument type="service" id="acme.product.catalog"/>
            <argument type="service" id="acme.sales.order"/>
            <tag name="mage.injectable"/>
        </service>

类本身将实现 __dependencies() 因此

    /**
     * @var Mage_Catalog_Model_Product
     */
    private $catalog;

    /**
     * @var Mage_Sales_Model_Order
     */
    private $order;

    /**
     * @param Mage_Catalog_Model_Product $catalog
     * @param Mage_Sales_Model_Order $order
     */
    public function __dependencies(
        Mage_Catalog_Model_Product $catalog,
        Mage_Sales_Model_Order $order
    ) {
        $this->catalog = $catalog;
        $this->order = $order;
    }

遗憾的是,由于扩展了 Mage_Core_Controller_Front_Action,您的控制器仍然依赖于 Mage,因此无法作为单元测试,但它至少会清楚地显示其依赖项,并且它们将具有类型提示。

要在实例化后向其他类提供依赖项,除了使用 mage.injectable 标签和实现 __dependencies 之外,您还必须覆盖您的类构造函数,例如

    function __construct()
    {
        Mage::getSingleton('inviqa_symfonyContainer/serviceInjector')->setupDepdendencies($this);
        parent::__construct();
    }

注册您自己的编译器传递

在您的项目中创建一个 自定义编译器传递类

创建一个 Magento 观察者,并将其配置为监听 symfony_container_before_container_generator Magento 事件。更新此观察者以将自定义编译器传递注入到生成器配置中,如下所示

class Inviqa_DependencyInjection_Model_Observer
{
    /** @var CustomCompilerPass */
    private $customCompilerPass;

    public function __construct()
    {
        $this->customCompilerPass = new CustomCompilerPass();
    }

    public function onBeforeContainerGenerator(Varien_Event_Observer $observer)
    {
        /** @var Configuration $generatorConfig */
        $generatorConfig = $observer->getData('generator_config');

        $generatorConfig->addCompilerPass($this->customCompilerPass);
    }
}