原文发布时间: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” 文件名构成。但是需要注意以下几点:
- Magento 的 module namespace 是
Magento
不再是Mage
core
,community
,local
code pools 不再有,所有模块都在app/code
下- 原来
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!
我们做的事情是
- 实例化
Pulsestorm\TutorialObjectManager1\Model\Example
- 调用实例化后对象的方法
getHelloWorld
取得字符串 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 (听上去有点自相矛盾)