什么是二次开发

所谓二次开发,即是在 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

  1. namespace Notadd\Foundation\Administration;
  2. use Illuminate\Container\Container;
  3. use Illuminate\Events\Dispatcher;
  4. use InvalidArgumentException;
  5. use Notadd\Foundation\Administration\Abstracts\Administrator;
  6. /**
  7. * Class Administration.
  8. */
  9. class Administration
  10. {
  11. /**
  12. * @var \Notadd\Foundation\Administration\Abstracts\Administrator
  13. */
  14. protected $administrator;
  15. /**
  16. * @var \Illuminate\Container\Container
  17. */
  18. protected $container;
  19. /**
  20. * @var \Illuminate\Events\Dispatcher
  21. */
  22. protected $events;
  23. /**
  24. * Administration constructor.
  25. *
  26. * @param \Illuminate\Container\Container $container
  27. * @param \Illuminate\Events\Dispatcher $events
  28. */
  29. public function __construct(Container $container, Dispatcher $events)
  30. {
  31. $this->container = $container;
  32. $this->events = $events;
  33. }
  34. /**
  35. * Get administrator.
  36. *
  37. * @return \Notadd\Foundation\Administration\Abstracts\Administrator
  38. */
  39. public function getAdministrator()
  40. {
  41. return $this->administrator;
  42. }
  43. /**
  44. * Status of administrator's instance.
  45. *
  46. * @return bool
  47. */
  48. public function hasAdministrator()
  49. {
  50. return is_null($this->administrator) ? false : true;
  51. }
  52. /**
  53. * Set administrator instance.
  54. *
  55. * @param \Notadd\Foundation\Administration\Abstracts\Administrator $administrator
  56. *
  57. * @throws \InvalidArgumentException
  58. */
  59. public function setAdministrator(Administrator $administrator)
  60. {
  61. if (is_object($this->administrator)) {
  62. throw new InvalidArgumentException('Administrator has been Registered!');
  63. }
  64. if ($administrator instanceof Administrator) {
  65. $this->administrator = $administrator;
  66. $this->administrator->init();
  67. } else {
  68. throw new InvalidArgumentException('Administrator must be instanceof ' . Administrator::class . '!');
  69. }
  70. }
  71. }

抽象类 Administrator

  1. namespace Notadd\Foundation\Administration\Abstracts;
  2. use Illuminate\Events\Dispatcher;
  3. use Illuminate\Routing\Router;
  4. use InvalidArgumentException;
  5. /**
  6. * Class Administrator.
  7. */
  8. abstract class Administrator
  9. {
  10. /**
  11. * @var \Illuminate\Events\Dispatcher
  12. */
  13. protected $events;
  14. /**
  15. * @var mixed
  16. */
  17. protected $handler;
  18. /**
  19. * @var string
  20. */
  21. protected $path;
  22. /**
  23. * @var \Illuminate\Routing\Router
  24. */
  25. protected $router;
  26. /**
  27. * Administrator constructor.
  28. *
  29. * @param \Illuminate\Events\Dispatcher $events
  30. * @param \Illuminate\Routing\Router $router
  31. */
  32. public function __construct(Dispatcher $events, Router $router)
  33. {
  34. $this->events = $events;
  35. $this->router = $router;
  36. }
  37. /**
  38. * Get administration handler.
  39. *
  40. * @return mixed
  41. */
  42. public function getHandler()
  43. {
  44. return $this->handler;
  45. }
  46. /**
  47. * Administration route path.
  48. *
  49. * @return string
  50. */
  51. public function getPath()
  52. {
  53. return $this->path;
  54. }
  55. /**
  56. * Init administrator.
  57. *
  58. * @throws \InvalidArgumentException
  59. */
  60. final public function init()
  61. {
  62. if (is_null($this->path) || is_null($this->handler)) {
  63. throw new InvalidArgumentException('Handler or Path must be Setted!');
  64. }
  65. $this->router->group(['middleware' => 'web'], function () {
  66. $this->router->get($this->path, $this->handler);
  67. });
  68. }
  69. /**
  70. * Register administration handler.
  71. *
  72. * @param $handler
  73. */
  74. public function registerHandler($handler)
  75. {
  76. $this->handler = $handler;
  77. }
  78. /**
  79. * Register administration route path.
  80. *
  81. * @param string $path
  82. */
  83. public function registerPath($path)
  84. {
  85. $this->path = $path;
  86. }
  87. }

IOC 实例注册方式

  1. namespace Notadd\Administration;
  2. use Illuminate\Support\ServiceProvider;
  3. use Notadd\Administration\Controllers\AdminController;
  4. use Notadd\Foundation\Administration\Administration;
  5. /**
  6. * Class Extension.
  7. */
  8. class ModuleServiceProvider extends ServiceProvider
  9. {
  10. /**
  11. * Boot service provider.
  12. *
  13. * @param \Notadd\Foundation\Administration\Administration $administration
  14. */
  15. public function boot(Administration $administration)
  16. {
  17. $administrator = new Administrator($this->app['events'], $this->app['router']);
  18. $administrator->registerPath('admin');
  19. $administrator->registerHandler(AdminController::class . '@handle');
  20. $administration->setAdministrator($administrator);
  21. }
  22. }

路由

可编程路由是 Laravel 的一大特性,而在 Notadd 中,允许以 Event 的形式进行扩展。

扩展示例

第一步:

在模块目录(如:modules/administration)的源码目录中的订阅者目录(src/Subscribers)中添加一个继承自类 Notadd\Foundation\Routing\Abstracts\RouteRegister 的事件订阅者类,类名可完全自定义,例如:RouteRegister

第二步:

在类 RouteRegister 实现 router 定义,示例代码如下:

  1. namespace Notadd\Content\Listeners;
  2. use Notadd\Content\Controllers\Api\Article\ArticleController as ArticleApiController;
  3. use Notadd\Content\Controllers\Api\Article\ArticleTemplateController as ArticleTemplateApiController;
  4. use Notadd\Content\Controllers\Api\Category\CategoryController as CategoryApiController;
  5. use Notadd\Content\Controllers\Api\Category\CategoryTemplateController as CategoryTemplateApiController;
  6. use Notadd\Content\Controllers\Api\Category\CategoryTypeController as CategoryTypeApiController;
  7. use Notadd\Content\Controllers\Api\Page\PageController as PageApiController;
  8. use Notadd\Content\Controllers\Api\Page\PageTemplateController as PageTemplateApiController;
  9. use Notadd\Content\Controllers\Api\Page\PageTypeController as PageTypeApiController;
  10. use Notadd\Content\Controllers\ArticleController;
  11. use Notadd\Content\Controllers\Api\Article\ArticleTypeController as ArticleTypeApiController;
  12. use Notadd\Content\Controllers\CategoryController;
  13. use Notadd\Content\Controllers\PageController;
  14. use Notadd\Foundation\Routing\Abstracts\RouteRegister as AbstractRouteRegister;
  15. /**
  16. * Class RouteRegister.
  17. */
  18. class RouteRegister extends AbstractRouteRegister
  19. {
  20. /**
  21. * Handle Route Registrar.
  22. */
  23. public function handle()
  24. {
  25. $this->router->group(['middleware' => ['auth:api', 'web'], 'prefix' => 'api/article'], function () {
  26. $this->router->resource('template', ArticleTemplateApiController::class);
  27. $this->router->resource('type', ArticleTypeApiController::class);
  28. $this->router->resource('/', ArticleApiController::class);
  29. });
  30. $this->router->group(['middleware' => ['auth:api', 'web'], 'prefix' => 'api/category'], function () {
  31. $this->router->resource('template', CategoryTemplateApiController::class);
  32. $this->router->resource('type', CategoryTypeApiController::class);
  33. $this->router->resource('/', CategoryApiController::class);
  34. });
  35. $this->router->group(['middleware' => ['auth:api', 'web'], 'prefix' => 'api/page'], function () {
  36. $this->router->resource('template', PageTemplateApiController::class);
  37. $this->router->resource('type', PageTypeApiController::class);
  38. $this->router->resource('/', PageApiController::class);
  39. });
  40. $this->router->group(['middleware' => 'web', 'prefix' => 'article'], function () {
  41. $this->router->resource('/', ArticleController::class);
  42. });
  43. $this->router->group(['middleware' => 'web', 'prefix' => 'category'], function () {
  44. $this->router->resource('/', CategoryController::class);
  45. });
  46. $this->router->group(['middleware' => 'web', 'prefix' => 'page'], function () {
  47. $this->router->resource('/', PageController::class);
  48. });
  49. }
  50. }

拓展

拓展一般需要在特定环境下实现,比如 Workerman 模块,需要安装 Workerman 支持的拓展。

新特性,开发中…

模块

模块是 Notadd 的功能实体,是区别于 notadd/framework 来说的,notadd/framework 仅是承载 Notadd 体系的逻辑实现,并没有包含功能性代码。

目录结构

模块位于目录 modules 下,每个模块在一个独立的文件夹内,模块内部的目录结构如下:

  1. # module/administration 模块目录
  2. # resources 资源目录
  3. # translations 翻译文件目录
  4. # views 视图目录
  5. # src 源码目录
  6. # Controllers 控制器目录
  7. # Subscribers 事件订阅者目录(支持自动发现)
  8. # ModuleServiceProvider.php 模块的服务提供者
  9. # composer.json Composer 配置文件
  10. # configuration.yaml 模块配置文件
  11. # readme.md 模块说明文件

Resources

Resources 目录是 Module 的资源类文件放置的目录,包含如下几个类型目录:

  • assets
  • translations
  • views

Assets

assets 目录为前端相关资源或项目的放置目录。

Translations

translations 目录为多语言资源文件的放置目录。

Views

views 目录为视图资源文件的放置目录。

ModuleServiceProvider

ModuleServiceProvider 是 Module 的模块入口文件,也 Module 的所有功能注入及组件启动的服务提供者。

完整示例

  1. namespace Notadd\Content;
  2. use Illuminate\Events\Dispatcher;
  3. use Illuminate\Support\ServiceProvider;
  4. use Notadd\Content\Events\RegisterArticleTemplate;
  5. use Notadd\Content\Events\RegisterArticleType;
  6. use Notadd\Content\Events\RegisterCategoryTemplate;
  7. use Notadd\Content\Events\RegisterCategoryType;
  8. use Notadd\Content\Events\RegisterPageTemplate;
  9. use Notadd\Content\Events\RegisterPageType;
  10. use Notadd\Content\Listeners\CsrfTokenRegister;
  11. use Notadd\Content\Listeners\RouteRegister;
  12. use Notadd\Content\Managers\ArticleManager;
  13. use Notadd\Content\Managers\CategoryManager;
  14. use Notadd\Content\Managers\PageManager;
  15. /**
  16. * Class Module.
  17. */
  18. class ModuleServiceProvider extends ServiceProvider
  19. {
  20. /**
  21. * Boot service provider.
  22. */
  23. public function boot()
  24. {
  25. $this->loadMigrationsFrom(realpath(__DIR__ . '/../databases/migrations'));
  26. $this->loadTranslationsFrom(realpath(__DIR__ . '/../resources/translations'), 'content');
  27. }
  28. /**
  29. * Register services.
  30. */
  31. public function register()
  32. {
  33. $this->app->alias('article.manager', ArticleManager::class);
  34. $this->app->alias('category.manager', CategoryManager::class);
  35. $this->app->alias('page.manager', PageManager::class);
  36. $this->app->singleton('article.manager', function ($app) {
  37. $manager = new ArticleManager($app, $app['events']);
  38. $this->app->make(Dispatcher::class)->fire(new RegisterArticleTemplate($app, $manager));
  39. $this->app->make(Dispatcher::class)->fire(new RegisterArticleType($app, $manager));
  40. return $manager;
  41. });
  42. $this->app->singleton('category.manager', function ($app) {
  43. $manager = new CategoryManager($app, $app['events']);
  44. $this->app->make(Dispatcher::class)->fire(new RegisterCategoryTemplate($app, $manager));
  45. $this->app->make(Dispatcher::class)->fire(new RegisterCategoryType($app, $manager));
  46. return $manager;
  47. });
  48. $this->app->singleton('page.manager', function ($app) {
  49. $manager = new PageManager($app, $app['events']);
  50. $this->app->make(Dispatcher::class)->fire(new RegisterPageTemplate($app, $manager));
  51. $this->app->make(Dispatcher::class)->fire(new RegisterPageType($app, $manager));
  52. return $manager;
  53. });
  54. }
  55. }

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 中。

完整示例

  1. {
  2. "name": "notadd/content",
  3. "description": "Notadd's Content Module.",
  4. "keywords": [
  5. "notadd",
  6. "cms",
  7. "framework",
  8. "content"
  9. ],
  10. "homepage": "https://notadd.com",
  11. "license": "Apache-2.0",
  12. "type": "notadd-module",
  13. "authors": [
  14. {
  15. "name": "twilroad",
  16. "email": "heshudong@ibenchu.com"
  17. }
  18. ],
  19. "require": {
  20. "php": ">=7.0"
  21. },
  22. "require-dev": {
  23. "notadd/installers": "0.5.*"
  24. },
  25. "autoload": {
  26. "psr-4": {
  27. "Notadd\\Content\\": "src/"
  28. }
  29. }
  30. }

模块配置文件

模块配置文件,是包含模块定义,模块版本定义,后台页面注入,后台仪表盘模块注入,资源目录注入等功能或特性实现的配置文件。

完整示例

  1. name: 后台管理 # 模块名称
  2. identification: notadd/administration # 模块标识,需和 composer.json 的 name 属性一致
  3. description: 'Notadd 后台管理模块' # 模块描述
  4. author: # 模块作者
  5. - twilroad
  6. - 269044570@qq.com
  7. version: 2.0.0 # 模块版本
  8. csrf: # 模块 CSRF 注入
  9. - 'admin*'
  10. - 'api*'
  11. - 'editor*'
  12. dashboards: # 后台仪表盘模块注入
  13. -
  14. identification: systeminfo
  15. title: 系统信息
  16. template: Notadd\Administration\SystemInformation@handler
  17. -
  18. identification: development
  19. title: 开发团队
  20. template:
  21. -
  22. tag: p
  23. content:
  24. -
  25. tag: strong
  26. content:
  27. - 开发团队:
  28. -
  29. attrs:
  30. href: https://github.com/twilroad
  31. target: _blank
  32. tag: a
  33. content:
  34. - 寻风
  35. -
  36. -
  37. attrs:
  38. href: https://www.zuohuadong.cn
  39. target: _blank
  40. tag: a
  41. content:
  42. - 依剑听雨
  43. -
  44. -
  45. attrs:
  46. href: https://github.com/medz
  47. target: _blank
  48. tag: a
  49. content:
  50. - Seven Du
  51. -
  52. -
  53. attrs:
  54. href: https://github.com/QiyueShiyi
  55. target: _blank
  56. tag: a
  57. content:
  58. - Luff
  59. -
  60. -
  61. attrs:
  62. href: https://weibo.com/u/2181906365
  63. target: _blank
  64. tag: a
  65. content:
  66. - 小莫
  67. -
  68. -
  69. attrs:
  70. href: https://weibo.com/u/3189357545
  71. target: _blank
  72. tag: a
  73. content:
  74. - 睡不醒的酸柠檬
  75. -
  76. -
  77. attrs:
  78. href: http://weibo.com/u/3854583077
  79. target: _blank
  80. tag: a
  81. content:
  82. - 浅殤
  83. -
  84. -
  85. attrs:
  86. href: https://weibo.cn/u/3258236872
  87. target: _blank
  88. tag: a
  89. content:
  90. - 马镝清同学
  91. -
  92. -
  93. attrs:
  94. href: http://weibo.com/u/5592753739
  95. target: _blank
  96. tag: a
  97. content:
  98. - 未央花事结
  99. -
  100. -
  101. tag: p
  102. content:
  103. -
  104. tag: strong
  105. content:
  106. - 开发团队:
  107. -
  108. attrs:
  109. href: https://github.com/LitoMore
  110. target: _blank
  111. tag: a
  112. content:
  113. - LitoMore
  114. -
  115. -
  116. attrs:
  117. href: https://github.com/ganlanshu0211
  118. target: _blank
  119. tag: a
  120. content:
  121. - 半缕阳光
  122. -
  123. -
  124. attrs:
  125. href: https://github.com/ToxinSting
  126. target: _blank
  127. tag: a
  128. content:
  129. - Rayle
  130. -
  131. -
  132. attrs:
  133. href: https://github.com/cloudsher
  134. target: _blank
  135. tag: a
  136. content:
  137. - cloudSher
  138. -
  139. -
  140. attrs:
  141. href: http://www.lovetd.cn
  142. target: _blank
  143. tag: a
  144. content:
  145. - 怒杀一只鸡
  146. -
  147. menus: # 后台菜单注入
  148. global:
  149. icon: settings
  150. permission:
  151. path: '/'
  152. text: 全局
  153. children:
  154. -
  155. icon: ios-cog
  156. text: 全局设置
  157. -
  158. children:
  159. -
  160. path: /upload
  161. text: 上传设置
  162. icon: ios-paper
  163. text: 附件设置
  164. -
  165. childrend:
  166. -
  167. path: /module
  168. text: 模块配置
  169. -
  170. path: /extension
  171. text: 插件配置
  172. -
  173. path: /template
  174. text: 模板配置
  175. -
  176. path: /expand
  177. text: 拓展配置
  178. icon: plus
  179. text: 应用管理
  180. -
  181. icon: plus
  182. text: 全局插件
  183. -
  184. childrend:
  185. -
  186. path: /menu
  187. text: 菜单管理
  188. -
  189. path: /seo
  190. text: SEO 管理
  191. -
  192. path: /mail
  193. text: 邮件设置
  194. -
  195. path: /debug
  196. text: 调试工具
  197. icon: stats-bars
  198. text: 系统插件
  199. pages: # 后台页面注入
  200. configurations:
  201. initialization:
  202. name: 参数配置
  203. tabs: true
  204. target: global
  205. tabs:
  206. configuration:
  207. default: true
  208. show: true
  209. submit: api/setting/set
  210. title: 全局设置
  211. fields:
  212. name:
  213. default: ''
  214. description: ''
  215. label: 网站名称
  216. key: site.name
  217. placeholder: 请输入网站名称
  218. required: true
  219. type: input
  220. validates:
  221. -
  222. message: 请输入网站名称
  223. required: true
  224. trigger: change
  225. type: string
  226. enabled:
  227. default: false
  228. description: '关闭后网站将不能访问'
  229. format: boolean
  230. label: 站点开启
  231. key: site.enabled
  232. required: false
  233. type: switch
  234. domain:
  235. default: ''
  236. description: '不带 http:// 或 https://'
  237. label: 网站域名
  238. key: site.domain
  239. required: false
  240. type: input
  241. multidomain:
  242. default: false
  243. description: '由于前后端分离机制,官方不对多域名做特殊支持,可能导致其他未知问题'
  244. format: boolean
  245. label: 开启多域名
  246. key: site.multidomain
  247. required: false
  248. type: switch
  249. beian:
  250. default: ''
  251. label: 备案信息
  252. key: site.beian
  253. required: false
  254. type: input
  255. company:
  256. default: ''
  257. label: 公司名称
  258. key: site.company
  259. required: false
  260. type: input
  261. copyright:
  262. default: ''
  263. label: 版权信息
  264. key: site.copyright
  265. required: false
  266. type: input
  267. statistics:
  268. default: ''
  269. label: 统计代码
  270. key: site.statistics
  271. required: false
  272. type: textarea
  273. publishes: # 资源目录映射
  274. assets/admin: resources/mixes/administration/dist/assets/admin

事件订阅者目录

事件订阅者模式,是 事件 机制中比较好用的一个模式,实现事件订阅者的自动发现,将能减少开发者的不少开发事件。

插件

说明

Extension 作为 Notadd Framework 的一个特性存在,允许通过 Extension 的方式对 Notadd Framework 进行功能或模板的扩展。 Extension 的机制类似于 LaravelService Provider 的机制,提供了一种实现组件化的机制,并可以实现传统插件机制中的安装、卸载以及插件启动过程。

基本结构

一个完整的 Notadd Extension ,必然是遵循 Composer 相关规范的 Package

目录结构

插件位于目录 extensions 下,插件目录结构如下

  1. # vendor 厂商目录
  2. # extension 插件目录
  3. # configuations 可加载配置文件目录
  4. # resources 资源目录
  5. # translations 翻译文件目录
  6. # views 视图目录
  7. # src 源码目录
  8. # Extension 扩展服务提供者定义文件
  9. # composer.json Composer 配置文件

其他说明

  • composer.json 中需定义 typenotadd-extension
  • composer.json 中需依赖 packagenotadd/installers

Extension 结构

Extension 的机制类似于 LaravelService Provider 的机制,提供了一种实现组件化的机制,并可以实现传统插件机制中的安装、卸载以及插件启动过程。

基本结构

一个完整的 Notadd Extension ,必然是遵循 Composer 相关规范的 Package

目录结构

插件位于目录 extensions 下,插件目录结构如下

  1. # vendor 厂商目录
  2. # extension 插件目录
  3. # configuations 可加载配置文件目录
  4. # resources 资源目录
  5. # translations 翻译文件目录
  6. # views 视图目录
  7. # src 源码目录
  8. # bootstrap.php 插件启动脚本
  9. # composer.json Composer 配置文件

其他说明

  • composer.json 中需定义 typenotadd-module
  • composer.json 中需依赖 packagenotadd/installers

Composer

通过对 Composer 的自定义,可以实现 Composer 自动加载 Extension 定义的依赖项。

Type

配置 type 属性为 notadd-extension。

Require

添加 notadd/installers 的 Package,才能实现 Composer 自动加载 Extension 定义的依赖项。

完整示例

  1. {
  2. "name": "notadd/extension-demo",
  3. "description": "Notadd's Demo Extension.",
  4. "type": "notadd-extension",
  5. "keywords": ["notadd", "demo", "extension"],
  6. "homepage": "https://notadd.com",
  7. "license": "Apache-2.0",
  8. "authors": [
  9. {
  10. "name": "twilroad",
  11. "email": "heshudong@ibenchu.com"
  12. }
  13. ],
  14. "autoload": {
  15. "psr-4": {
  16. "Notadd\\Demo\\": "src/"
  17. }
  18. },
  19. "require": {
  20. "php": ">=7.0",
  21. "notadd/installers": "0.5.*"
  22. }
  23. }