Magento 2 Object Manager(翻译)

原文网址

原文发布时间:2015年7月10日

Magento 2 相比较 1 有很多变化,尽管你还能看到 1 的影子(EAV, areas, blocks 等),但是 Magento 2 核心团队经过几年努力,已经将 1 的核心换成了更成熟的,“enterprise-java” 类型的系统,更多的类、更多的对象、更多的设计模式。

尽管有些人可能会认为 Magento 2 代码复杂性增加了,Magento 2 核心团队还是花了很多时间来简化和澄清 Magento 1 中的设计。Magento 1 中 rewrite 系统(home grown class rewrite system —— based around blocks, models, and helpers)已经由以来注入容器/对象管理器(Kiev grown dependency injection container/object-manager system)代替了。

如果你对 Magento 1 的开发方法,一直是复制粘贴,菜谱式(cookbook style)的,那么 Magento 2 对你来说就是一个新的框架,你以前的方法恐怕不管用了。

但是,如果你对 1 的开发,是带着理解设计模式的实现、培养阅读源码的能力进行的,那么跟上 Magento 2 就只是小小的“颠簸”——而且,许多方面,这个“颠簸”是为了让开发更加稳定、可预测。

本文是 Alan 解释 Magento 2 object manager/dependency-injection system 系列中的第一篇。我们将探索程序员在 Magento 2 中是怎样创建对象的,探索 Magento 2 的 object system 带来的额外特性,顺便讨论下相比较 Magento 1 的变化,探索 Magento 2 中的惯例。

Magento 2 命令行框架

Magento 2 的体系结构转变带来的巨大变化之一就是命令行框架。这不是从 Magento 1 中来的简单框架。相反,Magento 2 自带 Symfony’s Console component的完整实现。

安装完 Magento 2 后,打开终端,输入 php bin/magento,我们会看到很多系统自带的命令。

你应该会看到类似下面的输出:

Magento CLI version 0.74.0-beta16

Usage:
 command [options] [arguments]

Options:
 --help (-h)           Display this help message
 --quiet (-q)          Do not output any message
 --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal
                       output, 2 for more verbose output and 3 for debug
 --version (-V)        Display this application version
 --ansi                Force ANSI output
 --no-ansi             Disable ANSI output
 --no-interaction (-n) Do not ask any interactive question

Available commands:
 help                                      Displays help for a command
 list                                      Lists commands
 //... full command list snipped ...

除了自带许多有用的命令,第三方开发者也可以创建 Magento 模块来加入新的命令。

我们将使用命令行来运行本教程中的示例代码。命令行是一个对运行示例代码来说,很干净的好地方,我们不需要考虑你的代码到浏览器页面加载过程中经过的各种层,我们可以直接观察输出结果。

安装示例代码

Alan 准备了示例代码magento2-tutorial-objectmanager1,下载下来后解压出来,然后放到 app/code 目录中,他的目录结构应该是 app/code/Pulsestorm/TutorialObjectManager1/

然后运行下面的命令,应该会看到类似的输出:

$ php bin/magento module:status
List of enabled modules:
Magento_Store
//...

List of disabled modules:
Pulsestorm_TutorialObjectManager1

我们的模块现在还是 disabled 状态。下面我们打开 app/etc/config.php,然后我们会看到类似的内容:

#File: app/etc/config.php
<?php
return array (
  'modules' =>
  array (
    'Magento_Store' => 1,
    //... full module list snipped ...
    'Magento_Wishlist' => 1,
  ),
);

app/etc/config.php 返回模块的数组,我们现在看见的都是系统自带的模块,下面我们把我们的模块加到列表最后:

#File: app/etc/config.php
<?php
return array (
  'modules' =>
  array (
    'Magento_Store' => 1,
    //... full module list snipped ...
    'Magento_Wishlist' => 1,
    'Pulsestorm_TutorialObjectManager1' => 1,
  ),
);

这时候我们运行 module:status ,我们的模块就会在 enabled 列表中了

和 Magento 1 一样,模块的全名由 “namespace” 文件名和 “module name” 文件名构成。但是需要注意以下几点:

  1. Magento 的 module namespace 是 Magento 不再是 Mage
  2. core,community, local code pools 不再有,所有模块都在 app/code
  3. 原来 app/etc/modules 下的一堆 (module declaration files) xml 由更简单的,php 设置代替。

在我(Alan)看来,Mage 到 Magento 的变化,是中立的,但是移除 code pools 和模块申明文件是有利的变化。

虽然core,community, local code pools 设计让系统所有者可以通过 code pool overrides 快速实现修改,但是这也导致商家更加不愿意升级系统,还有如果开发者修改了某个类过多的隐含 contract ,那么很可能导致微妙的系统问题。

Magento 1 中的模块声明文件的本意可能是设置哪些模块是 enabled 哪些不是,Magento 2 则通过简单的 on/off 配置数组来实现。

运行命令行和清除缓存

ok 我们模块已经安装好了。下面我们得确保新命令 ps:tutorial-object-manager-1 添加到系统中了。在这之前,我们还有一件事情要做,那就是清理缓存。

从概念上来说,Magento 2 清理缓存和 Magento 1 中是一样的。第一次长长的运行后,Magento 会缓存结果,用于下次更快的载入。比如说,虽然我们已经把我们的新模块加入 configuration 中了,但是 Magento 的 configuration 被缓存了,所以系统实际上还不知道我们的模块哩。

我们可以通过 php bin/magento cache:clean 清理缓存。但是,尽管所有有类型的缓存都会被清除,但是还有一些缓存是没有类型的,所以清除命令结果并不是 100% 干净。Magento 1 包含很多没有类型的缓存(比如 Zend’s module column names),Magento 1 的缓存清理命令也不能删除这些没有类型的缓存。Magento 2 还很年轻,我们还没发现这些边缘情况,不过如果你不确定有没有清除干净的话,那么就可以使出终极手段 —— 手动移除 var/cache 目录下的所有文件。

$ rm -rf /path/to/magento/var/cache/*

此外,Magento 2 某些情况下会自动生成类,虽然严格意义上不是缓存,但是清理这些生成的类有时候对你来说也很重要。

$ rm -rf /path/to/magento/var/generation/*

在 production 系统中,这么做要慎重,慎重,慎重。

清完缓存后,让我们来看看我们的命令:

$ php bin/magento list

//...

 ps:tutorial-object-manager-1              A cli playground for testing
                                           commands
//...

下面运行这个命令看看:

$ php bin/magento ps:tutorial-object-manager-1
You did it!

恭喜你!你成功的手动安装了一个 Magento 模块。

当然也可以通过命令行的方式来启用模块。

$ php bin/magento module:enable Pulsestorm_TutorialObjectManager1
The following modules have been enabled:
- Pulsestorm_TutorialObjectManager1

To make sure that the enabled modules are properly registered, run
'setup:upgrade'.

Cache cleared successfully.
Generated classes cleared successfully.

Alert: Generated static view files were not cleared. You can clear them
using the --clear-static-content option. Failure to clear static view files
might cause display issues in the Admin and storefront.

上面作为理解,下面进入动手阶段。

修改我们的命令

Magento 2 中每个 cli 命令都是由 php 类实现的。

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php
protected function execute(InputInterface $input, OutputInterface $output)
{
    $output->writeln("You did it!");
}

而安后我们把它改成:

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php    
protected function execute(InputInterface $input, OutputInterface $output)
{
    $output->writeln("Hello World!");
}

然后再运行我们的命令:

$ php bin/magento ps:tutorial-object-manager-1
Hello World!

我们上面做的事情就是传递一个字符串给 writeln 方法(等同于 echo 或者 print)。

Magento 2 Objects

当我们想实例化一个对象的时候,我们使用 new 关键字,让我们再 execute 方法中实验一下。

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php
protected function execute(InputInterface $input, OutputInterface $output)
{
    $object = new \Pulsestorm\TutorialObjectManager1\Model\Example;
    $message = $object->getHelloMessage();
    $output->writeln(
        $message
    );
}

再运行下我们的命令,结果是这样的:

$ php bin/magento ps:tutorial-object-manager-1
Hello Magento!

我们做的事情是

  1. 实例化 Pulsestorm\TutorialObjectManager1\Model\Example
  2. 调用实例化后对象的方法 getHelloWorld 取得字符串
  3. writeln 输出字符串

到这里都是简单的 php 用法。如果你不熟悉 php 的命名空间,那么这篇文章可能对你有用,PHP Primer: Namespaces

Magento 2 中没有任何机制不允许你使用 new 来实例化对象。但是如果你使用 Magengo 2 的高级特性(automatic constructor dependency injection, object proxying, etc),那么你就需要使用 Magento 的 object manager。

试试下面的用法:

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php
protected function execute(InputInterface $input, OutputInterface $output)
{
    $manager = $this->getObjectManager();
    $object  = $manager->create('Pulsestorm\TutorialObjectManager1\Model\Example');
    $message = $object->getHelloMessage();
    $output->writeln(
        $message
    );
}

运行命令,我们应该可以看到下面的输出:

$ php bin/magento ps:tutorial-object-manager-1
Hello Magento!

和之前的区别就在下面两行代码上:

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php      
$manager = $this->getObjectManager();
$object  = $manager->create('Pulsestorm\TutorialObjectManager1\Model\Example');

第一行的 $manager = $this->getObjectManager(); 为我们取得 object manager 。这可不是开发插件时常用的做法 —— getObjectManager 是本次教程自己建的 helper 方法。现在我们只要知道 getObjectManager 会返回 Magento object manager 的实例。

object manager 是一个很特别的对象,Magento 中几乎所有的对象都是由它实例化的。代码中第二行做的就是实例化类

#File: app/code/Pulsestorm/TutorialObjectManager1/Command/Testbed.php
$object  = $manager->create('Pulsestorm\TutorialObjectManager1\Model\Example');

我们调用 object manager 的 create 方法,以字符串的形式传递一个 php 类给它。object manager 就实例化了一个 Pulsestorm\TutorialObjectManager1\Model\Example 对象给我们。

这就是 Magento 2 的 object manager 最简单的形式。看起来这样做无足轻重,但是如果所有对象的实例化都是通过它,那么 Magento 的系统工程师们就可以给这些对象 “超能力”。

在介绍它的超能力之前,本文不能涵盖所有内容,所以有兴趣的可以查看源代码,Magento 的 object manager 源码位于下列类文件中:

#File: lib/internal/Magento/Framework/ObjectManager/ObjectManager.php
namespace Magento\Framework\ObjectManager;

class ObjectManager implements \Magento\Framework\ObjectManagerInterface
{
    //...
}

如果你是 Magento 1 的开发者,那么 2 中的 object manager 就是 1 中以下方法的替代:

Mage::getModel(...);
Mage::helper(...);
Mage::getSingleton('core/layout')->createBlock(...);

Magento 2 中仍然存在 model,helper,block 这些概念,但是你再也不需要知道这个类的别名了(core/template, model/product, etc.)。object manager 可以实例化任何 php 类,不仅仅是 model, helper, block 对象。

自动的单例对象

今天我们将探讨 Magento 2 的 object manager 带给对象的超能力之一 —— 自动的单例对象。但是再讨论这个之前,我们得先明确单例的含义。

试试下面的代码:

$manager = $this->getObjectManager();
$object  = $manager->create('Pulsestorm\TutorialObjectManager1\Model\Example');
$object->message = 'Hello PHP!';
$output->writeln(
    $object->getHelloMessage()
);

$object  = $manager->create('Pulsestorm\TutorialObjectManager1\Model\Example');
$output->writeln(
    $object->getHelloMessage()
);

运行命令,我们会看到:

$ php bin/magento ps:tutorial-object-manager-1
Hello PHP!
Hello Magento!a

代码与前面的类似,只是添加了一些东西。首先我们使用 object manager 从类 Pulsestorm\TutorialObjectManager1\Model\Example 实例化一个对象。然后我们给这个对象的 message 设置一个自定义的字符串,最后把它打印出来。第一段输出的是我们自定义的 message ,而第二段是默认的 message。

下面我们不用 object manager 的 create 方法,而是用 get 方法。

$manager = $this->getObjectManager();
$object  = $manager->get('Pulsestorm\TutorialObjectManager1\Model\Example');
$object->message = 'Hello PHP!';
$output->writeln(
    $object->getHelloMessage()
);

$object  = $manager->get('Pulsestorm\TutorialObjectManager1\Model\Example');
$output->writeln(
    $object->getHelloMessage()
);

这次的结果就不同了。

$ php bin/magento ps:tutorial-object-manager-1
Hello PHP!
Hello PHP!

第二次没有打印默认的 message ,而是打印了我们在第一个对象上设置的自定义 message。这是怎么回事呢?因为第二个对象就是第一个对象。

这就是 Magento object manager 的自动单例特性。(单例就是指一个单例对象的类只有一个实例存在)。如果系统中该单例对象的类已经存在,再实例话这个单例对象的类,还是会返回已经存在的那一个。这通常是通过向类的构造函数添加特殊代码来实现的,但是通过 Magento 2 的 object manager ,任何类都可以变成单例。

就我们上面的例子来说,第二次我们调用 get 的时候,Magento 返回给我们的对象还是那个我们设置了自定义 message 的对象。

如果你了解 Magento1 ,那么 create/get 的区别类似于:

Mage::getModel(...);
Mage::getSingleton(...);

这两个的区别。但是,Magento 2 中这个特性是对所有 class 都有用的,不仅仅是 model 对象。

自动的单例(Automatic singletons)只是 Magento 2 object manager 带给对象的超能力之一。下次我们将探讨 automatic constructor parameter dependency injection (这可是个大招),还有为啥 Magento 官方文档告诫我们不要使用 object manager (听上去有点自相矛盾)

发表评论

电子邮件地址不会被公开。 必填项已用*标注