什么是二次开发
所谓二次开发,即是在 Notadd Framework 的基础上,对 Notadd Framework 的原有结构、原有功能进行调整或增强。
Notadd Framework 支持进行的二次开发内容。
Notadd 推荐的扩展方式
Notadd 的基本特性是,模块化,可扩展,可插拔,故此,和 Laravel 的基于 Composer 的可扩展方式是有区别的。
传统的 Laravel 的扩展方式
- 独立的 routes.php 实现路由的增加和修改
- 构建一个扩展包,通过服务提供者进行功能扩展和 IOC 容器实例注入
- 基于脚手架的功能扩展方式,如 Auth 的扩展,有对应的命令行生成源码的脚手架
从以上两种方式可以看出,Laravel 具备很强的自扩展能力,但是也存在以下几个弊端:
- 从项目的构建到部署,离不开开发者的参与,普通使用者无法拿到操作方式统一和简洁的开箱即用的源码
- 扩展包仅能实现固定业务底层的逻辑实现,与现有业务逻辑相融合或代码合并时,仍然需要二次编码,甚至不得不修改包的部分实现代码
Notadd 推荐的扩展方式
为可扩展、可插拔而生的 Notadd,很轻松的就在代码层上解决了以上弊端:
- 完全遵循 Composer 规范,轻松实现对任意第三方 Composer 包的引用
- 开箱即用的可插拔架构,减少开发者的结构构建时间
- 基于 OAuth2 的 API 验证技术,多平台,多方案实现,为 API 的安全保驾护航
- 完整的 RESUTFul API 规范,轻松实现对 API 的构建
- 提供多层次的可扩展架构,拓展(extension)、模块(module)、插件(addon)
功能性扩展说明列表
- Administrator
- 路由
- 拓展 特定环境拓展, 诸如 swoole拓展,PostgreSQL增强拓展
- 模块 大功能,诸如商城、文章、微信
- 插件 功能增强,诸如 全局短信,全局验证码。
Administrator
Administrator 作为唯一的网站管理实例,有着控制管理入口、分配管理职责等功能。
Notadd 的实现方式:
类 Administration
namespace Notadd\Foundation\Administration;use Illuminate\Container\Container;use Illuminate\Events\Dispatcher;use InvalidArgumentException;use Notadd\Foundation\Administration\Abstracts\Administrator;/*** Class Administration.*/class Administration{/*** @var \Notadd\Foundation\Administration\Abstracts\Administrator*/protected $administrator;/*** @var \Illuminate\Container\Container*/protected $container;/*** @var \Illuminate\Events\Dispatcher*/protected $events;/*** Administration constructor.** @param \Illuminate\Container\Container $container* @param \Illuminate\Events\Dispatcher $events*/public function __construct(Container $container, Dispatcher $events){$this->container = $container;$this->events = $events;}/*** Get administrator.** @return \Notadd\Foundation\Administration\Abstracts\Administrator*/public function getAdministrator(){return $this->administrator;}/*** Status of administrator's instance.** @return bool*/public function hasAdministrator(){return is_null($this->administrator) ? false : true;}/*** Set administrator instance.** @param \Notadd\Foundation\Administration\Abstracts\Administrator $administrator** @throws \InvalidArgumentException*/public function setAdministrator(Administrator $administrator){if (is_object($this->administrator)) {throw new InvalidArgumentException('Administrator has been Registered!');}if ($administrator instanceof Administrator) {$this->administrator = $administrator;$this->administrator->init();} else {throw new InvalidArgumentException('Administrator must be instanceof ' . Administrator::class . '!');}}}
抽象类 Administrator
namespace Notadd\Foundation\Administration\Abstracts;use Illuminate\Events\Dispatcher;use Illuminate\Routing\Router;use InvalidArgumentException;/*** Class Administrator.*/abstract class Administrator{/*** @var \Illuminate\Events\Dispatcher*/protected $events;/*** @var mixed*/protected $handler;/*** @var string*/protected $path;/*** @var \Illuminate\Routing\Router*/protected $router;/*** Administrator constructor.** @param \Illuminate\Events\Dispatcher $events* @param \Illuminate\Routing\Router $router*/public function __construct(Dispatcher $events, Router $router){$this->events = $events;$this->router = $router;}/*** Get administration handler.** @return mixed*/public function getHandler(){return $this->handler;}/*** Administration route path.** @return string*/public function getPath(){return $this->path;}/*** Init administrator.** @throws \InvalidArgumentException*/final public function init(){if (is_null($this->path) || is_null($this->handler)) {throw new InvalidArgumentException('Handler or Path must be Setted!');}$this->router->group(['middleware' => 'web'], function () {$this->router->get($this->path, $this->handler);});}/*** Register administration handler.** @param $handler*/public function registerHandler($handler){$this->handler = $handler;}/*** Register administration route path.** @param string $path*/public function registerPath($path){$this->path = $path;}}
IOC 实例注册方式
namespace Notadd\Administration;use Illuminate\Support\ServiceProvider;use Notadd\Administration\Controllers\AdminController;use Notadd\Foundation\Administration\Administration;/*** Class Extension.*/class ModuleServiceProvider extends ServiceProvider{/*** Boot service provider.** @param \Notadd\Foundation\Administration\Administration $administration*/public function boot(Administration $administration){$administrator = new Administrator($this->app['events'], $this->app['router']);$administrator->registerPath('admin');$administrator->registerHandler(AdminController::class . '@handle');$administration->setAdministrator($administrator);}}
路由
可编程路由是 Laravel 的一大特性,而在 Notadd 中,允许以 Event 的形式进行扩展。
扩展示例
第一步:
在模块目录(如:modules/administration)的源码目录中的订阅者目录(src/Subscribers)中添加一个继承自类 Notadd\Foundation\Routing\Abstracts\RouteRegister 的事件订阅者类,类名可完全自定义,例如:RouteRegister。
第二步:
在类 RouteRegister 实现 router 定义,示例代码如下:
namespace Notadd\Content\Listeners;use Notadd\Content\Controllers\Api\Article\ArticleController as ArticleApiController;use Notadd\Content\Controllers\Api\Article\ArticleTemplateController as ArticleTemplateApiController;use Notadd\Content\Controllers\Api\Category\CategoryController as CategoryApiController;use Notadd\Content\Controllers\Api\Category\CategoryTemplateController as CategoryTemplateApiController;use Notadd\Content\Controllers\Api\Category\CategoryTypeController as CategoryTypeApiController;use Notadd\Content\Controllers\Api\Page\PageController as PageApiController;use Notadd\Content\Controllers\Api\Page\PageTemplateController as PageTemplateApiController;use Notadd\Content\Controllers\Api\Page\PageTypeController as PageTypeApiController;use Notadd\Content\Controllers\ArticleController;use Notadd\Content\Controllers\Api\Article\ArticleTypeController as ArticleTypeApiController;use Notadd\Content\Controllers\CategoryController;use Notadd\Content\Controllers\PageController;use Notadd\Foundation\Routing\Abstracts\RouteRegister as AbstractRouteRegister;/*** Class RouteRegister.*/class RouteRegister extends AbstractRouteRegister{/*** Handle Route Registrar.*/public function handle(){$this->router->group(['middleware' => ['auth:api', 'web'], 'prefix' => 'api/article'], function () {$this->router->resource('template', ArticleTemplateApiController::class);$this->router->resource('type', ArticleTypeApiController::class);$this->router->resource('/', ArticleApiController::class);});$this->router->group(['middleware' => ['auth:api', 'web'], 'prefix' => 'api/category'], function () {$this->router->resource('template', CategoryTemplateApiController::class);$this->router->resource('type', CategoryTypeApiController::class);$this->router->resource('/', CategoryApiController::class);});$this->router->group(['middleware' => ['auth:api', 'web'], 'prefix' => 'api/page'], function () {$this->router->resource('template', PageTemplateApiController::class);$this->router->resource('type', PageTypeApiController::class);$this->router->resource('/', PageApiController::class);});$this->router->group(['middleware' => 'web', 'prefix' => 'article'], function () {$this->router->resource('/', ArticleController::class);});$this->router->group(['middleware' => 'web', 'prefix' => 'category'], function () {$this->router->resource('/', CategoryController::class);});$this->router->group(['middleware' => 'web', 'prefix' => 'page'], function () {$this->router->resource('/', PageController::class);});}}
拓展
拓展一般需要在特定环境下实现,比如 Workerman 模块,需要安装 Workerman 支持的拓展。
新特性,开发中…
模块
模块是 Notadd 的功能实体,是区别于 notadd/framework 来说的,notadd/framework 仅是承载 Notadd 体系的逻辑实现,并没有包含功能性代码。
目录结构
模块位于目录 modules 下,每个模块在一个独立的文件夹内,模块内部的目录结构如下:
# module/administration 模块目录# resources 资源目录# translations 翻译文件目录# views 视图目录# src 源码目录# Controllers 控制器目录# Subscribers 事件订阅者目录(支持自动发现)# ModuleServiceProvider.php 模块的服务提供者# composer.json Composer 配置文件# configuration.yaml 模块配置文件# readme.md 模块说明文件
Resources
Resources 目录是 Module 的资源类文件放置的目录,包含如下几个类型目录:
- assets
- translations
- views
Assets
assets 目录为前端相关资源或项目的放置目录。
Translations
translations 目录为多语言资源文件的放置目录。
Views
views 目录为视图资源文件的放置目录。
ModuleServiceProvider
ModuleServiceProvider 是 Module 的模块入口文件,也 Module 的所有功能注入及组件启动的服务提供者。
完整示例
namespace Notadd\Content;use Illuminate\Events\Dispatcher;use Illuminate\Support\ServiceProvider;use Notadd\Content\Events\RegisterArticleTemplate;use Notadd\Content\Events\RegisterArticleType;use Notadd\Content\Events\RegisterCategoryTemplate;use Notadd\Content\Events\RegisterCategoryType;use Notadd\Content\Events\RegisterPageTemplate;use Notadd\Content\Events\RegisterPageType;use Notadd\Content\Listeners\CsrfTokenRegister;use Notadd\Content\Listeners\RouteRegister;use Notadd\Content\Managers\ArticleManager;use Notadd\Content\Managers\CategoryManager;use Notadd\Content\Managers\PageManager;/*** Class Module.*/class ModuleServiceProvider extends ServiceProvider{/*** Boot service provider.*/public function boot(){$this->loadMigrationsFrom(realpath(__DIR__ . '/../databases/migrations'));$this->loadTranslationsFrom(realpath(__DIR__ . '/../resources/translations'), 'content');}/*** Register services.*/public function register(){$this->app->alias('article.manager', ArticleManager::class);$this->app->alias('category.manager', CategoryManager::class);$this->app->alias('page.manager', PageManager::class);$this->app->singleton('article.manager', function ($app) {$manager = new ArticleManager($app, $app['events']);$this->app->make(Dispatcher::class)->fire(new RegisterArticleTemplate($app, $manager));$this->app->make(Dispatcher::class)->fire(new RegisterArticleType($app, $manager));return $manager;});$this->app->singleton('category.manager', function ($app) {$manager = new CategoryManager($app, $app['events']);$this->app->make(Dispatcher::class)->fire(new RegisterCategoryTemplate($app, $manager));$this->app->make(Dispatcher::class)->fire(new RegisterCategoryType($app, $manager));return $manager;});$this->app->singleton('page.manager', function ($app) {$manager = new PageManager($app, $app['events']);$this->app->make(Dispatcher::class)->fire(new RegisterPageTemplate($app, $manager));$this->app->make(Dispatcher::class)->fire(new RegisterPageType($app, $manager));return $manager;});}}
Composer 配置文件
通过对 Composer 的自定义,可以实现 Notadd 风格的目录结构。
Type
配置 type 属性为 notadd-module,会告诉 Composer Installer 将该 Package 安装到目录 modules 下,而非默认目录 vendor 下。
Require
添加 notadd/installers 的 Package,才能调整 Composer 对该类型 Package 的默认处理逻辑,实现重定向安装目录的特性。
介于,模块的安装方式有两种,一种方式是:将 Composer Package 写入程序根项目目录下的 composer.json 文件,另一种方法是,单独初始化模块 Package,并以文件夹的形式放到 modules 目录,并且,需要使用命令 composer install —no-dev 进行初始化。
故此,包 notadd/installers 应放置在 require-dev 中。
完整示例
{"name": "notadd/content","description": "Notadd's Content Module.","keywords": ["notadd","cms","framework","content"],"homepage": "https://notadd.com","license": "Apache-2.0","type": "notadd-module","authors": [{"name": "twilroad","email": "heshudong@ibenchu.com"}],"require": {"php": ">=7.0"},"require-dev": {"notadd/installers": "0.5.*"},"autoload": {"psr-4": {"Notadd\\Content\\": "src/"}}}
模块配置文件
模块配置文件,是包含模块定义,模块版本定义,后台页面注入,后台仪表盘模块注入,资源目录注入等功能或特性实现的配置文件。
完整示例
name: 后台管理 # 模块名称identification: notadd/administration # 模块标识,需和 composer.json 的 name 属性一致description: 'Notadd 后台管理模块' # 模块描述author: # 模块作者- twilroad- 269044570@qq.comversion: 2.0.0 # 模块版本csrf: # 模块 CSRF 注入- 'admin*'- 'api*'- 'editor*'dashboards: # 后台仪表盘模块注入-identification: systeminfotitle: 系统信息template: Notadd\Administration\SystemInformation@handler-identification: developmenttitle: 开发团队template:-tag: pcontent:-tag: strongcontent:- 开发团队:-attrs:href: https://github.com/twilroadtarget: _blanktag: acontent:- 寻风- ,-attrs:href: https://www.zuohuadong.cntarget: _blanktag: acontent:- 依剑听雨- ,-attrs:href: https://github.com/medztarget: _blanktag: acontent:- Seven Du- ,-attrs:href: https://github.com/QiyueShiyitarget: _blanktag: acontent:- Luff- ,-attrs:href: https://weibo.com/u/2181906365target: _blanktag: acontent:- 小莫- ,-attrs:href: https://weibo.com/u/3189357545target: _blanktag: acontent:- 睡不醒的酸柠檬- ,-attrs:href: http://weibo.com/u/3854583077target: _blanktag: acontent:- 浅殤- ,-attrs:href: https://weibo.cn/u/3258236872target: _blanktag: acontent:- 马镝清同学- ,-attrs:href: http://weibo.com/u/5592753739target: _blanktag: acontent:- 未央花事结- 。-tag: pcontent:-tag: strongcontent:- 开发团队:-attrs:href: https://github.com/LitoMoretarget: _blanktag: acontent:- LitoMore- ,-attrs:href: https://github.com/ganlanshu0211target: _blanktag: acontent:- 半缕阳光- ,-attrs:href: https://github.com/ToxinStingtarget: _blanktag: acontent:- Rayle- ,-attrs:href: https://github.com/cloudshertarget: _blanktag: acontent:- cloudSher- ,-attrs:href: http://www.lovetd.cntarget: _blanktag: acontent:- 怒杀一只鸡- 。menus: # 后台菜单注入global:icon: settingspermission:path: '/'text: 全局children:-icon: ios-cogtext: 全局设置-children:-path: /uploadtext: 上传设置icon: ios-papertext: 附件设置-childrend:-path: /moduletext: 模块配置-path: /extensiontext: 插件配置-path: /templatetext: 模板配置-path: /expandtext: 拓展配置icon: plustext: 应用管理-icon: plustext: 全局插件-childrend:-path: /menutext: 菜单管理-path: /seotext: SEO 管理-path: /mailtext: 邮件设置-path: /debugtext: 调试工具icon: stats-barstext: 系统插件pages: # 后台页面注入configurations:initialization:name: 参数配置tabs: truetarget: globaltabs:configuration:default: trueshow: truesubmit: api/setting/settitle: 全局设置fields:name:default: ''description: ''label: 网站名称key: site.nameplaceholder: 请输入网站名称required: truetype: inputvalidates:-message: 请输入网站名称required: truetrigger: changetype: stringenabled:default: falsedescription: '关闭后网站将不能访问'format: booleanlabel: 站点开启key: site.enabledrequired: falsetype: switchdomain:default: ''description: '不带 http:// 或 https://'label: 网站域名key: site.domainrequired: falsetype: inputmultidomain:default: falsedescription: '由于前后端分离机制,官方不对多域名做特殊支持,可能导致其他未知问题'format: booleanlabel: 开启多域名key: site.multidomainrequired: falsetype: switchbeian:default: ''label: 备案信息key: site.beianrequired: falsetype: inputcompany:default: ''label: 公司名称key: site.companyrequired: falsetype: inputcopyright:default: ''label: 版权信息key: site.copyrightrequired: falsetype: inputstatistics:default: ''label: 统计代码key: site.statisticsrequired: falsetype: textareapublishes: # 资源目录映射assets/admin: resources/mixes/administration/dist/assets/admin
事件订阅者目录
事件订阅者模式,是 事件 机制中比较好用的一个模式,实现事件订阅者的自动发现,将能减少开发者的不少开发事件。
插件
说明
Extension 作为 Notadd Framework 的一个特性存在,允许通过 Extension 的方式对 Notadd Framework 进行功能或模板的扩展。 Extension 的机制类似于 Laravel 中 Service Provider 的机制,提供了一种实现组件化的机制,并可以实现传统插件机制中的安装、卸载以及插件启动过程。
基本结构
一个完整的 Notadd Extension ,必然是遵循 Composer 相关规范的 Package。
目录结构
插件位于目录 extensions 下,插件目录结构如下
# vendor 厂商目录# extension 插件目录# configuations 可加载配置文件目录# resources 资源目录# translations 翻译文件目录# views 视图目录# src 源码目录# Extension 扩展服务提供者定义文件# composer.json Composer 配置文件
其他说明
- composer.json 中需定义 type 为 notadd-extension
- composer.json 中需依赖 package 为 notadd/installers
Extension 结构
Extension 的机制类似于 Laravel 中 Service Provider 的机制,提供了一种实现组件化的机制,并可以实现传统插件机制中的安装、卸载以及插件启动过程。
基本结构
一个完整的 Notadd Extension ,必然是遵循 Composer 相关规范的 Package。
目录结构
插件位于目录 extensions 下,插件目录结构如下
# vendor 厂商目录# extension 插件目录# configuations 可加载配置文件目录# resources 资源目录# translations 翻译文件目录# views 视图目录# src 源码目录# bootstrap.php 插件启动脚本# composer.json Composer 配置文件
其他说明
- composer.json 中需定义 type 为 notadd-module
- composer.json 中需依赖 package 为 notadd/installers
Composer
通过对 Composer 的自定义,可以实现 Composer 自动加载 Extension 定义的依赖项。
Type
配置 type 属性为 notadd-extension。
Require
添加 notadd/installers 的 Package,才能实现 Composer 自动加载 Extension 定义的依赖项。
完整示例
{"name": "notadd/extension-demo","description": "Notadd's Demo Extension.","type": "notadd-extension","keywords": ["notadd", "demo", "extension"],"homepage": "https://notadd.com","license": "Apache-2.0","authors": [{"name": "twilroad","email": "heshudong@ibenchu.com"}],"autoload": {"psr-4": {"Notadd\\Demo\\": "src/"}},"require": {"php": ">=7.0","notadd/installers": "0.5.*"}}
