Visitar URL original
EvaEngine

AlloVince is Evil

EVAEngine

Yet another development engine based on Phalcon

1.1 Why Phalcon

根据业务做技术选型,我们的需求是什么

  1. 产品形态非常复杂(新闻、行情、社区)
  2. 必须快
  3. 松耦合,每个人能独立承担业务

  1. 小框架还是大框架
  2. 有没有可能组合两个框架
  3. 对于PHP重框架有什么优化手段

1.2.1 段子

  • ZF2:我有官方支持
  • Phalcon:我快
  • Symfony:我有数不清的Bundle
  • Phalcon:我快
  • Laravel:我代码书写无比优雅
  • Phalcon:我快

天下武功、唯快不破

1.2.2 优缺点

优点

  • 性能提升6~15倍
  • 框架功能够用,可以解决70%的需求
  • DI设计优秀,扩展性好

缺点

  • 黑盒
  • 辅助工具简陋
  • 部署运维及测试略麻烦
  • 一些小坑

1.3 Phalcon存在的问题及坑

  1. 默认启动的DI太简单,不能满足大型项目需求
  2. 模块实现较弱,模块加载在路由之后
  3. View不够友好,强制关联Layout和Template,没有考虑多模块
  4. Form太简陋,没有考虑数据关联的情况
  5. 一些奇怪的行为(没有View时不抛异常、Dispatch抛异常得到白屏等)

2.1 EvaEngine所要实现的

修改原则:避免深度定制,避免覆盖接口。通过扩展新方法及调用事件为主

  1. 内置配置好的DI,将系统逻辑简化到配置文件级别 √
  2. 真正意义上的模块化 √
  3. View的扩展,支持模块间调用 √
  4. Form的扩展,Form间可以关联 √
  5. 统一异常处理封装 √
  6. 模块间的协同处理(考虑插件?)
  7. 方便的数据库迁移工具(含ORM映射)
  8. 更好的调试工具(Router/Events)

目标:所有资源集中到一个框架,通过组合模块完成80%的基础工作

2.2 EvaEngine的层级划分

- Application (Web) 每个Application都有唯一的Name
  - Application Config
    - Module Load Config 命名根据Application Name
    - Global Config
    - Local Config
  - Module
    - Module Bootstrap File
    - Module Config
    - Controller
      - Admin Controller
    - Model
      - Entity
    - Form
    - Events Listener 
    - View
      - Helper
      - Partial
      - Admin View
  - Another Module
- Another Application (API)
                    

2.3 EvaEngine的目录结构

- apps #存放Application
- cache #数据/页面缓存
- config #全局配置/配置文件缓存
- modules #存放公共模块
  - EvaUser #模块
    - Module.php #模块启动文件
    - config #模块配置/前台路由/后台路由
    - views  #模块Views
      - layouts #布局
      - index/index #模板
      - partial #组件
      - admin #后台
        - layouts #布局
        - partial #组件
    - src #源代码
      - EvaUser #PSR-2
        - Controllers
          - Admin #后台
        - Models
        - Entities
        - Forms
        - Events
    - Utils
        - View
          - Helpers
- public #入口文件夹
- utilities #辅助工具
- workers #后台任务

2.4 EvaEngine的启动代码

use Eva\EvaEngine\Engine;

//设置Application根目录, 设置AppName
$engine = new Engine(__DIR__ . '/..', 'AppName'); 

$engine
->loadModules(include __DIR__ . '/../config/modules.' . $engine->getAppName() . '.php') 
//根据AppName加载模块配置文件,初始化DI
->bootstrap() //初始化Application,注册模块事件,注册ErrorHandler
->run(); //Phalcon\Application->handle()

3.1 EvaEngine的Module

如何定义模块

  1. 具有独立的一组功能,可以支持一个最小规模的产品(低耦合,高内聚)
  2. 有自己的MVC
  3. 有自己的管理后台
  4. 有自己的前台路由(可选)
  5. 有自己的后台路由(可选)
  6. 模块启动文件必须实现接口Eva\EvaEngine\Module\StandardInterface

3.2 Module的加载过程

  1. 通过DI实例化EvaEngine\Module\ModuleManager
  2. 设置Module默认路径$moduleManager->setDefaultPath()
  3. 正式加载$moduleManager->setDefaultPath()
    1. 触发事件module:beforeLoadModule
    2. 配置模块,注册模块启动文件
    3. 注册全局加载器Module::registerGlobalAutoloaders()[缓存]
    4. 注册全局事件Module::registerGlobalEventListeners()[缓存]
    5. 注册全局HelperModule::registerGlobalViewHelpers()[缓存]
    6. 注册跨模块的Entity关系Module::registerGlobalRelations()[缓存]
    7. 触发事件module:afterLoadModule
  4. Phalcon原始模块启动过程

3.3 如何配置一个模块

默认配置

$engine->loadModules(array("EvaUser"));

自定义配置

$engine->loadModules(array(
"EvaUser" => array( //模块名
    'className' => 'Eva\EvaUser\Module', //模块启动文件Class名
    'path' => __DIR__ . '/../modules/EvaUser/Module.php', //模块启动文件路径
    'moduleConfig' => '/../modules/EvaUser/config/config.php', //模块配置文件路径
    'routesFrontend' =>  '/../modules/EvaUser/config/routes.frontend.php', //模块前台路由配置文件路径
    'routesBackend' =>  '/../modules/EvaUser/config/routes.backend.php', //模块后台路由配置文件路径
    'adminMenu' => '', //模块后台导航文件
)));

4.1 默认配置的DI,其一

  • ModuleManager : 模块管理器 (EvaEngine实现)
  • EventsManager : 事件管理器(已启用优先级)
  • Config : 全局配置
  • Router : 路由
  • Dispatcher : 分发器(事件已注入)
  • ModelsMetadata : 表结构(默认开启文件缓存)
  • ModelsManager: Model管理器(默认开启主从)
  • View : View (EvaEngine\View)
  • Session : Session(默认使用文件)
  • DbMaster / DbSlave : 数据库主从
  • ViewCache / ModelCache / ApiCache: 默认均为文件
  • Queue : 队列,默认为GearmanClient
  • Worker : 默认为GearmanWorker
  • Mailer : 邮件发送服务,默认为Swift_Mailer
  • MailMessage: 邮件主题,支持模板,EvaEngine\MailMessage

4.2 默认配置的DI,其二

  • Url : Url生成器
  • Escaper : 转义
  • Tag : View标签 EvaEngine\Tag
  • Flash : 提示信息处理
  • Placeholder : 模板占位符
  • Cookies : 默认关闭加密
  • Translate : I18N实现,默认使用csv
  • FileSystem : 文件服务,基于Gaufrette

启动DI的推荐方法:

$this->getDI()->getModuleManager();

不推荐:

$this->getDI()->get('moduleManager');

5.1 异常处理

什么时候用异常:当主要业务无法继续进行(注册、登录、访问资源不存在)

什么时候不用异常:伴随主业务的附加服务(注册发邮件、用户得积分)

  • 所有异常会抛至顶层,由Eva\EvaEngine\ErrorHandler统一接管
  • ErrorHandler可替换
  • Debug开启时,ErrorHandler为Phalcon\Debug
  • ErrorHandler根据Exception不同,产生不同响应(Http Status Code / HTML / JSON)
  • EvaEngine的异常必须实现接口Eva\EvaEngine\Exception\ExceptionInterface。异常中携带了Http Status Code

5.2 异常的归类

- StandardException                      #标准异常     500
  - LogicException                       #逻辑异常     500
    - BadFunctionCallException           #错误函数调用 400
    - BadMethodCallException             #错误方法调用 405
    - DomainException                    #Domain错误   400 
    - InvalidArgumentException           #错误的参数   400 
    - LengthException                    #长度错误     400 
    - OutOfRangeException                #超出范围     400
    - OperationNotPermitedException      #操作不允许   403 
    - ResourceConflictException          #资源冲突     409 
    - ResourceExpiredException           #资源过期     403 
    - ResourceNotFoundException          #资源不存在   404
    - UnauthorizedException              #资源未授权   401
    - VerifyFailedException              #验证失败     403
  - RuntimeException                     #运行异常     500
    - IOException                        #IO错误       500 
                    

5.3 异常举例

  • 用户名重复 ResourceConflictException 409
  • 输入API参数错误 InvalidArgumentException 400
  • 访问不存在的文章 ResourceNotFoundException 404
  • POST接口使用GET访问 BadMethodCallException 405
  • 未登录访问后台 UnauthorizedException 401
  • 修改无权修改的资源 OperationNotPermitedException 403
  • 文件无法写入 IOException 500
  • 核心错误 RuntimeException 500

错误代码规范: ERR_模块_异常信息

throw new Exception\UnauthorizedException('ERR_USER_NOT_ACTIVATED');

5.4 内部错误代码

待定

6.1 对Controller的扩展

通过不同的接口实现来区分数据类型:HTML/JSON,权限:Session/Token

  • JsonControllerInterface 数据自动支持Json/Jsonp
  • SessionAuthorityControllerInterface 基于Session验证
  • TokenAuthorityControllerInterface 基于Token验证

7.1 对Model的扩展

  • 默认开启主从
  • 支持表前缀,默认为`eva_`
  • initialize()会默认加载跨模块的Entity关系
  • dump()用于将带有关系的Entity输出为数组

8.1 对View的扩展

基于模块名设置模版

$view->setModuleLayout('EvaCommon', '/views/admin/layouts/layout');

基于模块名设置View的根目录

$view->setModuleViewsDir('EvaBlog', '/views');

基于模块名设置区块的根目录

$view->setModulePartialsDir('EvaCommon', '/views');

9.1 对Form的扩展

Form可以绑定Model

protected $defaultModelClass = 'Eva\EvaBlog\Models\Text';

Form间可以关联

        $form = new Forms\PostForm();
        $post = new Models\Post();
        $form->setModel($post);
        $form->addForm('text', 'Eva\EvaBlog\Forms\TextForm');

关联后统一验证,验证完毕通过Form保存所有关联数据

        $data = $this->request->getPost();
        if (!$form->isFullValid($data)) {
            $form->save('updatePost');
        }

子表单的输出会自动添加前缀

$form->getForm('text')->render('content');
<input name="text[content]" />

可以通过注解及绑定Model自动初始化Form Elemens

    /**
     * @Type(Select)
     * @Option(deleted=Deleted)
     */
    public $status;

10.1 后台的UI控件

待定

11.1 未解决的问题

  • 如何应对字段的变更
  • 简单可行的CMS缓存策略
  • 多对多及复杂情况的Form处理