mandrills / magento2-tutorial
A simple blog module for magento 2.2.6
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
Open Issues: 0
Type:magento2-module
Requires
- php: ^7.1.3
- magento/magento-composer-installer: *
This package is auto-updated.
Last update: 2019-03-08 08:54:32 UTC
README
一个简单的 Magento II 博客模块
简介
Magento PHP 开发者需要快速学习如何在平台上构建我们的客户商店并构建模块。这个是个简单的博客模块,将通过这个模块学习如何创建一个完整的 Magento 2 模块,后台管理和单元测试。
通过这个博客学习,你能轻松的完成以下操作:
- 创建可通过 Composer 安装的模块
- 创建控制器并了解重写系统的工作原理
- 块、布局和模板如何工作
- 创建模型并与数据库交互
- 设置后台创建、编辑和删除操作的管理界面
- 创建单元测试 (待补充)
这个模块能做什么?
最终模块将是一个非常基本的博客,你将能够通过管理员创建博客文章,包括编辑和删除他们。然后从前端你将能够查看所有博客文章的列表,并能单独查看每个文章。这些操作能够涵盖构建 Magento 扩展的所有必要元素。
模块创建步骤
基本模块设置
设置我们模块的基本结构:
etc/module.xml
registration.php
composer.json
在根目录中,我们创建一个 composer.json
文件,它看起来像这样:
{
"name": "mandrills/magento2-tutorial",
"description": "A simple blog module for magento 2.2.6",
"type": "magento2-module",
"version": "1.0.0",
"license": [
"OSL-3.0",
"AFL-3.0"
],
"authors": [
{
"name": "Andy",
"email": ""
}
],
"require": {
"php": "~7.1.*",
"magento/magento-composer-installer": "*"
},
"extra": {
"map": [
[
"*",
"Tutorial/Blog"
]
]
}
}
Comoposer文件这里不解释太多,不太了解的同学请查看它的官方文档。这里主要说下文件里 type 和 extra 字段。
"type": "magento2-module"
定义我们repo的类型为magento2模块
"extra": { "map": [ [ "*", "Tutorial/Blog" ] ] }
定义composer如何安装这个模块。 翻译过来就是所有文件都应该在Tutorial/Blog文件夹中,这就意味着我们的模块将被安装到app/code/Tutorial/Blog
。
如果想了解更多有关Composer相关的文章,请访问Alan Kent的博客。
首先你先创建etc/module.xml文件
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Tutorial_Blog" setup_version="1.0.0" />
</config>
接着我们需要在模块的根目录中创建registration.php文件,Magento中使用它来注册模块。
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Tutorial_Blog',
__DIR__
);
现在一个基本的模块已经形成,但是它在Magento中还不能运行。要使模块正常使用,就需要启用它并升级数据库,像这样:
bin/magento module:status # 展示每个模块的状态:关闭/开启
bin/magento module:enable Tutorial_Blog # 开启Tutorial_Blog模块
bin/magento setup:upgrade # 升级数据库,保存当前模块的版本以及需要安装的表
bin/magento module:status # 确认当前模块状态
如果不使用composer安装的话,到项目下创建app/code目录(如果目录不存在需要创建),然后将代码复制到app/code/Tutorial/Blog目录结构中即可。
app
code
Tutorial
Blog
registration.php
etc
module.xml
设置模型和资源模型
让我们进入今天的主题,知识点主要涵盖:
- 模型
- 资源模型
Magento有很多自己的惯例,今天就开始慢慢涵盖进来,第一个就是使用PHP接口。博客模块只需要一个数据表就可以了,并且被命名为tutorial_blog_post,使用命名空间所以就不必担心冲突产生。 我们的模型就成为Post,所以在创建模型之前我们要创建一个接口,这个接口我们称之为数据接口。
如果你要了解更多关于如何使用接口,可以移步至devdocs.magento.com查看Magento官方文档。
在构建数据接口之前,先把博客数据表post
使用的列罗列出来:
- post_id - post表主键ID
- url_key - 唯一 url_key,可以自定义创建post路由
- title - post标题
- content - post内容
- created_at - 创建时间
- updated_at - 更新时间
- published_at - 发布时间
- is_active - post是否开启标记
现在我们开始创建Api/Data/PostInterface
数据接口,代码如下:
<?php
namespace Tutorial\Blog\Api\Data;
interface PostInterface
{
const POST_ID = 'post_id';
const URL_KEY = 'url_key';
const TITLE = 'title';
const CONTENT = 'content';
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
const PUBLISHED_AT = 'published_at';
const IS_ACTIVE = 'is_active';
/**
* @return int|null
*/
public function getId();
/**
* @return string
*/
public function getUrlKey();
/**
* @return string|null
*/
public function getTitle();
/**
* @return string|null
*/
public function getContent();
/**
* @return string|null
*/
public function getCreatedAt();
/**
* @return string|null
*/
public function getUpdatedAt();
/**
* @return string|null
*/
public function getPublishedAt();
/**
* @return boolean|null
*/
public function isActive();
/**
* @param $id
* @return $this
*/
public function setId($id);
/**
* @param string $url_key
* @return $this
*/
public function setUrlKey($url_key);
/**
* @param string $title
* @return $this
*/
public function setTitle($title);
/**
* @param string $content
* @return $this
*/
public function setContent($content);
/**
* @param string $created_at
* @return $this
*/
public function setCreatedAt($created_at);
/**
* @param string $updated_at
* @return $this
*/
public function setUpdatedAt($updated_at);
/**
* @param string $published_at
* @return $this
*/
public function setPublishedAt($published_at);
/**
* @param int|bool $is_active
* @return $this
*/
public function setIsActive($is_active);
}
这个接口定义了所有的getter和setter,这个以后和模型交互时使用。接着创建我们的模型Model/Post.php文件:
<?php
namespace Tutorial\Blog\Model;
use Tutorial\Blog\Api\Data\PostInterface;
use Magento\Framework\DataObject\IdentityInterface;
class Post extends \Magento\Framework\Model\AbstractModel implements PostInterface, IdentityInterface
{
const STATUS_ENABLED = 1;
const STATUS_DISABLED = 0;
const CACHE_TAG = 'blog_post';
protected $_cacheTag = 'blog_post';
protected $_eventPrefix = 'blog_post';
protected function _construct()
{
$this->_init('Tutorial\Blog\Model\ResourceModel\Post');
}
/**
* @return array|string[]
*/
public function getIdentities()
{
return [self::CACHE_TAG . '_' . $this->getId()];
}
/**
* @param string $url_key
* @return boolean
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function checkUrlKey($url_key)
{
return $this->_getResource()->checkUrlKey($url_key);
}
/**
* @return array
*/
public function getAvailableStatuses()
{
return [
self::STATUS_ENABLED => __('Enabled'),
self::STATUS_DISABLED => __('Disabled')
];
}
/**
* @return int|null
*/
public function getId()
{
return $this->getData(self::POST_ID);
}
/**
* @return string
*/
public function getUrlKey()
{
return $this->getData(self::URL_KEY);
}
/**
* @return null|string
*/
public function getTitle()
{
return $this->getData(self::TITLE);
}
/**
* @return null|string
*/
public function getContent()
{
return $this->getData(self::CONTENT);
}
/**
* @return null|string
*/
public function getCreatedAt()
{
return $this->getData(self::CREATED_AT);
}
/**
* @return null|string
*/
public function getUpdatedAt()
{
return $this->getData(self::UPDATED_AT);
}
/**
* @return null|string
*/
public function getPublishedAt()
{
return $this->getData(self::PUBLISHED_AT);
}
/**
* @return bool|null
*/
public function isActive()
{
return $this->getData(self::IS_ACTIVE);
}
/**
* @param mixed $id
* @return PostInterface|Post
*/
public function setId($id)
{
return $this->setData(self::POST_ID, $id);
}
/**
* @param string $url_key
* @return PostInterface|Post
*/
public function setUrlKey($url_key)
{
return $this->setData(self::URL_KEY, $url_key);
}
/**
* @param string $title
* @return PostInterface|Post
*/
public function setTitle($title)
{
return $this->setData(self::TITLE, $title);
}
/**
* @param string $content
* @return mixed|PostInterface
*/
public function setContent($content)
{
return $this->setData(self::CONTENT, $content);
}
/**
* @param string $created_at
* @return PostInterface|Post
*/
public function setCreatedAt($created_at)
{
return $this->setData(self::CREATED_AT, $created_at);
}
/**
* @param string $updated_at
* @return PostInterface|Post
*/
public function setUpdatedAt($updated_at)
{
return $this->setData(self::UPDATED_AT, $updated_at);
}
/**
* @param string $published_at
* @return PostInterface|Post
*/
public function setPublishedAt($published_at)
{
return $this->setData(self::PUBLISHED_AT, $published_at);
}
/**
* @param bool|int $is_active
* @return PostInterface|Post
*/
public function setIsActive($is_active)
{
return $this->setData(self::IS_ACTIVE, $is_active);
}
}
正如你看到的一样,Post模型实现了PostInterface中所有的方法,同时我们也实现了第二个接口Magento\Framework\DataObject\IdentityInterface
。这个接口被用于模型创建更新删除操作后缓存的刷新,以及在前端模型的渲染。只需要实现getIdentities()
方法即可,这将返回这个模型的唯一ID号,这就是该模型的缓存。
现在是时候创建我们的资源模型Model/ResourceModel/Post.php
了。
<?php
namespace Tutorial\Blog\Model\ResourceModel;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
class Post extends AbstractDb
{
protected $date;
public function __construct(
\Magento\Framework\Model\ResourceModel\Db\Context $context,
\Magento\Framework\Stdlib\DateTime\DateTime $date,
?string $connectionName = null)
{
$this->date = $date;
parent::__construct($context, $connectionName);
}
protected function _construct()
{
$this->_init('tutorial_blog_post','post_id');
}
/**
* @param \Magento\Framework\Model\AbstractModel $object
* @return $this
* @throws LocalizedException
*/
protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object)
{
if (!$this->isValidPostUrlKey($object)) {
throw new LocalizedException(__('The post URL key contains capital letters or disallowed symbols.'));
}
if ($this->isNumericPostUrlKey($object)) {
throw new LocalizedException(__('The post URL key cannot be made of only numbers.'));
}
if ($object->isObjectNew() && !$object->hasCreatedAt()) {
$object->setCreatedAt($this->date->gmtDate());
}
$object->setUpdatedAt($this->date->gmtDate());
return parent::_beforeSave($object);
}
/**
* @param \Magento\Framework\Model\AbstractModel $object
* @param mixed $value
* @param string $field
* @return $this
*/
public function load(\Magento\Framework\Model\AbstractModel $object, $value, $field = null)
{
if (!is_numeric($value) && is_null($field)) {
$field = 'url_key';
}
return parent::load($object, $value, $field);
}
/**
* @param string $field
* @param mixed $value
* @param \Magento\Framework\Model\AbstractModel $object
* @return \Magento\Framework\DB\Select
*/
protected function _getLoadSelect($field, $value, $object)
{
$select = parent::_getLoadSelect($field, $value, $object);
if ($object->getStoreId()) {
$select->where(
'is_active = ?',
1
)->limit(
1
);
}
return $select;
}
/**
* @param string $url_key
* @param int $isActive
* @return \Magento\Framework\DB\Select
* @throws LocalizedException
*/
protected function _getLoadByUrlKeySelect($url_key, $isActive = null)
{
$select = $this->getConnection()->select()->from(
['bp' => $this->getMainTable()]
)->where(
'bp.url_key = ?',
$url_key
);
if (!is_null($isActive)) {
$select->where('bp.is_active = ?', $isActive);
}
return $select;
}
/**
* @param \Magento\Framework\Model\AbstractModel $object
* @return false|int
*/
protected function isValidPostUrlKey(\Magento\Framework\Model\AbstractModel $object)
{
return preg_match('/^[a-z0-9][a-z0-9_\/-]+(\.[a-z0-9_-]+)?$/', $object->getData('url_key'));
}
/**
* @param \Magento\Framework\Model\AbstractModel $object
* @return false|int
*/
protected function isNumericPostUrlKey(\Magento\Framework\Model\AbstractModel $object)
{
return preg_match('/^[0-9]+$/', $object->getData('url_key'));
}
/**
* @param $url_key
* @return int
* @throws LocalizedException
*/
public function checkUrlKey($url_key)
{
$select = $this->_getLoadByUrlKeySelect($url_key, 1);
$select->reset(\Zend_Db_Select::COLUMNS)->columns('bp.post_id')->limit(1);
return $this->getConnection()->fetchOne($select);
}
}
最后,我们需要资源模型集合,主要用来过滤模型和获取模型集合 Model/ResourceModel/Post/Collection.php
。
<?php
namespace Tutorial\Blog\Model\ResourceModel\Post;
class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
{
protected $_idFieldName = 'post_id';
protected function _construct()
{
$this->_init('Tutorial\Blog\Model\Post', 'Tutorial\Blog\Model\ResourceModel\Post');
}
}
设置数据库和迁移
前面已经创建了模型和资源模型,以便我们和数据库进行交互。但是我们还没有创建数据库表,这一部分将解决这个问题。
还记得etc/module.xml
中setup_version
吗?这个是告诉我们当前模块的版本,并决定是否需要运行升级和设置脚本。
让我们创建Stup类来安装我们的数据库吧!
创建Setup/InstallSchema.php
文件
<?php
namespace Tutorial\Blog\Setup;
class InstallSchema implements \Magento\Framework\Setup\InstallSchemaInterface
{
public function install(
\Magento\Framework\Setup\SchemaSetupInterface $setup,
\Magento\Framework\Setup\ModuleContextInterface $context
)
{
$installer = $setup;
$installer->startSetup();
//START table setup
$table = $installer->getConnection()->newTable(
$installer->getTable('tutorial_blog_post')
)->addColumn(
'post_id',
\Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
null,
[ 'identity' => true, 'nullable' => false, 'primary' => true, 'unsigned' => true, ],
'Post ID'
)->addColumn(
'url_key',
\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
100,
[ 'nullable' => true, 'default' => null ],
'Url Key'
)->addColumn(
'title',
\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
255,
[ 'nullable' => false, ],
'Blog Title'
)->addColumn(
'content',
\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
'2M',
[],
'Blog Content'
)->addColumn(
'created_at',
\Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
null,
[ 'nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT, ],
'Creation Time'
)->addColumn(
'updated_at',
\Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
null,
[ 'nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE, ],
'Modification Time'
)->addColumn(
'published_at',
\Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
null,
[ 'nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE, ],
'Published Time'
)->addColumn(
'is_active',
\Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
null,
[ 'nullable' => false, 'default' => '1', ],
'Is Post Active'
)->addIndex(
$installer->getIdxName('blog_post', ['url_key']), ['url_key']
)->setComment('Tutorial Blog Posts');
$installer->getConnection()->createTable($table);
//END table setup
$installer->endSetup();
}
}
这个类的名称可以自定,只要实现Magento\Framework\Setup\InstallSchemaInterface
这个接口就可以了;如果要升级版本怎么办?和你想的一样简单实现Magento\Framework\Setup\UpgradeSchemaInterface
即可。
如果你在升级之前检查当前模块的版本,了解更多有关升级问题可以查看stackoverflow上面的这篇文档。
现在前往CLI并运行bin/magento
命令来检查我们安装的数据库表。
当你运行命令并看到如下内容时:
$ bin/magento setup:db-schema:upgrade
Schema creation/updates:
Module 'Tutorial_Blog':
Installing schema..
这就表明Schema Setup安装成功了。