inviqa / magento-symfony-container
为Magento提供Symfony DI容器的实例
Requires
Requires (Dev)
- phpspec/phpspec: ^2.2
This package is not auto-updated.
Last update: 2024-09-14 18:04:12 UTC
README
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); } }