# hymie-mvc
**Repository Path**: mahaixing/hymie-mvc
## Basic Information
- **Project Name**: hymie-mvc
- **Description**: 支持依赖注入的简单php库,可用于学习和小微项目使用。
- **Primary Language**: PHP
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2020-02-21
- **Last Updated**: 2024-12-15
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
完整的手册请参考 [Hymie PHPMVC framework 手册](http://hymie.iautoo.cn/)
# 变更历史
| 版本号 | 说明 |
| ----- | --- |
| v1.0 | 实现MVC 框架、BeanFactory、路由、过滤器等功能 |
| v1.1 | 实现通过注解方式配置路由 |
| v1.2 | 1. 修改缓存工厂实现 2. 增加 APCu 缓存 3. 修改 BeanFactory、Router、Filter 默认缓存
为 APCu 缓存 4. 调整 Router 处理方式 5. 调整清理缓存方法|
| v2.0.2 | 增加 twig delimiter 配置 |
| v2.0.5 | 控制器文件名需要以 Controller.php 结尾 |
# 框架介绍
Hymie PHPMVC 是一个轻量级 MVC 框架,实现中借鉴了 [webpy](http://webpy.org/) 的一些设计思路。Hymie 的目标是实现基本的 MVC 模式,规范化的开发目录结构约定,并且易于整合和使用第三方库、框架。Hymie 框架代码文件大小约为 270KB。
框架遵循以下 PSR 标准:
1. `PSR-2`
2. `PSR-4`
3. `PSR-6`
4. `PSR-16`
> 这个帮助文档站点就是使用海米 PHPMVC 框架开发的,代码地址 Github 或者 Gitee
> 如果有改进建议或 BUG 反馈,请联系 mahaixing@gmail.com
## 约定
1. `web_root` 指的是 `web` 应用的主目录
2. `document_root` 指的是 Apache 的 `DocumentRoot` 或者 nginx 的 `root`。
## 一、安装
### 1.1 Composer
`composer require hymie/mvc`
代码中引用 `config.php` 即可完成框架配置
### 1.2 源码安装
可以从 github 或者 gitee 下载源码使用框架,下载后需要把框架目录放到 `web_root` 下。
`git clone https://github.com/mahaixing/hymie-mvc`
`git clone https://gitee.com/mahaixing/hymie-mvc`
保存下载的目录到项目主目录,代码中需要手工注册 Hymie 的 autoload,用于加载框架类。
```
composer 安装很慢的话,可以使用 阿里云 Composer 全量镜像
### 1.4 源码目录说明
```
hymie -+
|--- docs --- api (api文档)
|--- src (源代码)
|--- tests (单元测试代码)
|--- .gitignore
|--- composer.json
|--- config.bean.sample.php (bean 配置示例)
|--- config.filter.sample.php (过滤器配置示例)
|--- config.router.sample.php (路由配置示例)
|--- config.sample.php (配置文件示例)
|--- hymie (hymie 脚本,如果 composer 安装可以 ./vendor/bin/hymie 调用)
|--- phpunit.xml (phpunit 测试套件编排文件)
--- README.md
```
### 二、快速入门
### 2.1 新建项目
1. 在 `document_root` 下新建目录 `mkdir example` 并进入目录 `cd example`
2. 执行命令:
```
composer require hymie/mvc
```
3. composer 会安装框架及依赖库。
### 2.2 使用 hymie 脚本拷贝配置、创建模块
1. 拷贝配置文件
`./vendor/bin/hymie copy .`
2. 新建模块 `web`
`./vendor/bin/hymie add web .`
### 2.3 编写入口文件
1. 新建 `index.php` 并放入以下内容
```
run();
```
_现在的项目目录结构_
```
document_root
|
+ -- example
|
|-- app -+
| |
| - web -+
| |- bean (可选)
| |- controller (可选)
| |- service (可选)
| |- view (必须, 默认的模块模板路径)
| |- filter (可选)
| |- dao (可选)
| |- config.filter.php (过滤器配置, 可选)
| - config.router.php (路由配置, 可选)
|-- index.php (入口文件)
|-- config.php (配置文件)
-- config.bean.php (bean 配置文件)
```
### 2.4 编写代码
1. 编写控制器
新建 `app/web/controller/IndexController.php' 并放入以下内容
```
add("msg", $msg)->setView("index")->success();
}
}
```
2. 编写视图
新建 `app/web/view/index.php` 并放入以下内容
```
example index
```
3. 配置路由
修改 `app/web/config.router.php` 添加路由
```
'web\controller\IndexController'
);
```
### 2.4 运行
访问浏览器地址 `http://localhost/example/index.php`, ok 大功告成。
## 三、核心组件
### 3.1 正则表达式
Hymie 框架开发过程需要大量(至少在路由、过滤器定义中)使用正则表达式,使用正则表达式定义路由可以在一定程度上减少 xss 和 sql 注入类型攻击(参考: 路由部分的 PathVariable 介绍)。
另外,正则表达式也是每个程序员均应该掌握的基本能力。
Hymie 框架使用 PHP pcre 相关函数处理正则表达式,具体请参考 PCRE。
### 3.2 MVC
Hymie 框架实现了 MVC 设计模式:
```
前端控制器 ------> 具体控制器 ----> Service -----> Dao
| ^ | ^ | ^ |
| | | | | | |
| (view)-------- (业务数据)-- (data)<----
|
渲染视图
|
|
v
浏览器
```
#### 3.2.1 前端控制器
前端控制器是应用入口,Hymie 框架允许使用多个前端控制器,比如 `index.php` 是站点 web 端前端控制器,`admin.php` 是后前端控制器口,`api.php` 为接口前端控制器。
#### 3.2.2 控制器
控制器为遵循 PSR-4 规范的 PHP 类,控制器默认的方法为 `index`。控制器方法可以有参数,参数需要与路由定义的正则表达式匹配,如果表达式中包含可选的匹配项时,参数需要有默认值。
#### 3.2.3 Service
Service 用来实现业务逻辑。控制数据库事务。Service 为遵循 PSR-4 规范的 PHP 类,在控制器中直接实例化 Service 类即可。
> Service 使用具体类即可,其实无必要使用类似于 `ServiceInterface` `ServiceImpl` 这种方式。
> 所以,不建议使用 bean 工厂来管理 Service 类,配置工作量太大,而且对于大部分业务来说,其实也没有必要做过多解耦。
#### 3.2.4 Dao
建议使用 `PDO` 或者 `Medoo` 来处理数据库操作,配合框架提供的分页能力,可以很容易完成数据操作、分页等业务需求。分页请参考相关章节。
#### 3.2.5 视图
目前框架支持 `json`、`php`、`twig` 三种视图。在配置文件中可以配置默认视图(参考配置章节),视图也可以混用(参考视图章节)。如果请求是 ajax 请求,则会默认返回 json 视图。
框架支持用户自定义视图,在配置文件中注册即可,详细参考视图章节。
> 视图唯一的要求就是需要在 view_root 目录下按照模块名组织目录,每个模块目录下必须有 `view` 子目录用来存放视图文件。
#### 3.2.6 Result 对象
控制器执行后可以返回 `\hymie\Result` 对象,`\hymie\Result` 对象支持链式调用,并且如果是 ajax 请求的话, json 视图会根据 `\hymie\Result` 对象组织 json 数据。
如果控制器不返回 `\hymie\Result` 对象,那么控制器需要自行输出数据到浏览器或者使用 `R($to)` 函数跳转网页。
#### 3.2.7 URL 模式
支持两种 URL 模式,PATHINFO 和 QueryString,在 `config.php` 中的 `url` 部分进行配置。`url` 配置会影响到 `\hymie\Url` 类生成链接的方式。
> 注: nginx 需要进行配置才能支持 PATHINFO,请参考 **服务器配置示例部分**
> 注: 如果要生成 url_rewrite 的 url, 需要在配置文件的 `url` 部分设置 `url_rewrite` = true
### 3.3 路由配置
框架支持两种路由配置方式:
1. 基于配置文件的路由配置
```
return array(
'/' => '[module_dir_name]\controller\IndexController:someMethod',
// 分页, 支持
// http://example.com/index.php?g=/a/1/10 (第1页,每页10条)
// http://example.com/index.php?g=/a/1 (第1页,每页条数使用默认值10条)。
'/product/list/(\d+)(?:(?:/)(\d+))?' => ['mod1\controller\ProductController', 'list'],
*/);
```
2. 基于注解的路由配置。
```
use hymie\annotation\RouterMapping;
/**
* @RouterMapping(value="/(\w*)")
*/
class SomeController
{
public function index($name)
{
// handle path '/abc' '/def' /ghi
// $name = "abc" or "def" or "ghi"
}
}
```
```
use hymie\annotation\RouterMapping;
/**
* @RouterMapping(value="/other")
*/
class SomeOtherController
{
public function index()
{
// handle path '/other'
}
/**
* @RouterMapping(value="/foo-(\d{1,3})")
*/
public function login($number)
{
// handle path '/other/foo-1" "/other/foo-123"
// $number = 1 or $number = 123
//could not handle '/ohter/foo-1234'
}
}
```
### 3.4 过滤器
过滤器针对配置的路由进行过滤,支持正则表达式匹配路由,支持正则表达式匹配特定路由地址的排除。
* 当请求后过滤器会以链式方式逐个匹配路由,如不匹配或者匹配后过滤器方法返回 true 则由下一个过滤器继续处理。
* 当路由无法通过过滤器逻辑,过滤器可使用 `R($to)` 函数跳转页面,也可直接返回 `\hymie\Result` 对象。
**过滤器是按照定义顺序执行的,因此定义多个过滤器时要注意顺序。**
### 3.5 Bean 工厂
框架中提供 Bean 工厂能力,在 `config.bean.php` 中配置 bean 信息,由 bean 工厂负责实例化对象。
#### 3.5.1 bean 配置
bean 工厂支持的配置:
1. bean 间依赖:
beana 依赖 beanb
2. 循环依赖(有条件):
beana 依赖 beanb,且 beanb 也依赖 beana
3. 构造函数:
通过构造函数创建对象实例
4. 工厂方法:
通过工厂方法创建对象实例
4. 属性赋值:
为对象实例属性赋值(public、private、protected 实例都可赋值)
5. 函数调用:
实例创建后要执行的函数
>配置文件中配置的 bean 默认是单例的
#### 3.5.2 根据类名创建类
bean 工厂可以直接根据类名创建类实例。
>通过类名创建的类实例默认不是单例的,可以在调用 `get_bean` 方法时指定是否需要单例实例。
bean 工厂详细信息请参考 bean 工厂章节
### 3.6 分页
Hymie 框架基于适配器模式提供了通用的分页能力,分页主类为 `\hymie\pager\Pager` 通过该类的工厂方法 `public static function getPager($adapterObjOrClassName, ...$params)` 创建实例。
分页类在 $_GET $_POST $_REQUEST 中查找 `config.php` 配置文件中指定的分页参数 `p` 页数 `s` 每页数据条数获取分页参数。
目前分页类支持 `\PDO` 以及 `Medoo` 分页适配器。
对于 `\PDO` 适配器已实现 `Mysql` SQL 方言,其他数据库类型后续会逐步完成。
具体请参考分页章节。
### 3.7 缓存
框架的缓存部分支持 PSR-6 PSR-16 标准缓存库,会将所有 PSR-6 缓存适配为 PSR-16 缓存接口,框架实现了
1. `ArrayCache` 基于数组的缓存,在单次请求中有效
2. `ApcuCache` 基于 APCu 的缓存,单台服务器实例有效。
3. `Psr6Adapter` 适配 PSR-6 缓存实现到 PSR-16 接口规范
#### 3.7.1 `Cache` 工厂
框架通过 `Cache` 工厂来为应用和框架本身生产缓存实例,工厂方法原型为:
```
public static function getInstance(
$beanNameOrClassName = self::DEFAULT_BAEN_NAME,
$replaceBeanNameOrClassName = null,
$useBeanFactory = false,
$cleanable = false
)
```
其中:
1. `$beanNameOrClassName`: 要初始化的缓存 `bean` 名或者类名。
2. `$replaceBeanNameOrClassName`: 如果要初始化的缓存 `bean` 或类无法加载,则替换的缓存实现,默认是 `ArrayCache`
如果替换缓存实现也无法加载,则最终会返回 `ArrayCache` 实例,以保证使用缓存的代码无需做过多可用性判断,以及可测试性。
3. `$useBeanFactory`: 是否使用 BeanFactory,默认是 true,否则会直接 new 类
4. `$cleanable`: 是否可清理,默认是否,如果为 true 的话会用 `\hymie\cache\impl\CleanableCache` 包装生成的 bean
Application 启动时会调用 Cache::registerCleaner 函数,在脚本结束时检查 ROOT . DIRECTORY_SEPARATOR .clean_cache 文件,如存在则会清理 CleanableCache 实例。
> 注意:由于 PSR-6 PSR-16 标准没有遍历所有 key 的方法,因此清理实际上会清除整个缓存,目前框架的 `BeanFactory` `Router` `Filter` 是可清理的缓存。
> `BeanFactory` 默认使用 `ApcuCache` 如不可用则会使用 `ArrayCache`,
> `Router` `Filter` 默认使用 `ApcuCache` 如不可用则会使用 `config.bean.php` 中配置的缓存实现,默认是系统缓存。
> **建议启用 php 的 `apcu` 来获取更好的性能**
### 3.8 日志
1. 系统日志不能在 `config.bean.php` 中配置, 因为 `BeanFactory` 中使用了日志, 会发生递归调用.
2. 日志使用 `monolog`, 在 `config.php` 中进行配置. 若未配置则默认使用 `\Psr\Log\NullLogger`, 因此所有输出日志的代码不会出错.
3. 日志配置项参考 `config.sample.php` 中相关注释.
4. 可以在 `config.bean.php` 中配置应用要使用的日志 bean.
### 3.9 RedisSession
框架实现了 RedisSession, 可以在 `config.php` 中配置 `$config['session']['redis'] = true;` 来开启.
实现类为 `\hymie\session\RedisSession` 使用 [predis](https://packagist.org/packages/predis/predis) 与 redis 交互。默认会在 `config.bean.php` 中查找名为 `predis` 的 bean 配置,如未找到则抛出 `\hymie\session\SessionException`.
代码中需要使用 `start_session()` 函数来启动 session, 该函数会根据配置选择启动 php session 或者 redis session. **建议使用此函数来启动session, 而不是内置的 [`session_start`](https://www.php.net/session_start) 函数**
> Web 环境是一个多线程(或进程)的并发环境. 为了保证数据一致性,会对每次页面请求的会话进行加锁. 因此, 需要尽量快的完成 Session 的取值和赋值操作. 这不仅针对 RedisSession 对于其他 Session 实现也适用.