kicoephp / src
a simple web framework.
Requires
- php: >=7.4
- ext-json: *
This package is not auto-updated.
Last update: 2025-02-01 06:52:17 UTC
README
一个非常简单小巧 (仅由9个类组成) 的 php web 框架.
Install
composer require kicoephp/src
Dash
nginx 配置:
location / {
try_files $uri $uri/ /index.php?$query_string;
}
<?php require __DIR__ . './vendor/autoload.php'; use kicoe\core\Link; $link = new Link(); $link->route('/hello/{word}', function (string $word) { return "hello ".$word; }); $link->start();
Route
新版本框架源于自己一次写的快速路由前缀树实现, 总之就是非常快!⚡swoole版本
Route::get('/art/{page}', 'Article@list'); Route::get('/art/{id}/comments', 'Comment@list'); Route::get('/art/detail/{id}', 'Article@detail');
上面的 route 定义将被解析成以下结构:
{ "GET": { "path": "/", "handler": [], "children": { "a": { "path": "art/", "handler": [], "children": { "$": { "path": "page|id", "handler": ["Article", "list"], "children": { "c": { "path": "comments", "handler": ["Comment", "list"], "children": [] } } }, "d": { "path": "detail/", "handler": [], "children": { "$": { "path": "id", "handler": ["Article", "detail"], "children": [] } } } } } } } }
Routing
<?php use kicoe\core\Link; use kicoe\core\Route; use app\controller\ArticleController; $link = new Link(); // 自动解析类方法注释 eg: @route get /index/{id} Route::parseAnnotation(ArticleController::class); // 一般 routing $link->route('/article/tag/{tag_id}/page/{page}/', [ArticleController::class, 'listByTag']); // 闭包 $link->route('/comment/up/{art_id}', function (Request $request, int $art_id) { $email = $request->input('email'); ... }, 'post'); $link->start();
自定义基础类型
在路由参数映射到控制器方法参数时,将会自动将 string 转换成定义的类型,同时允许自定义:
<?php $link = new Link(); $link->bind('array', function (string $value):array { return explode(',', $value); }); $link->bind('bool', function (string $value):bool { if ($value === 'false') { return false; } else if ($value === 'true') { return true; } return false; // ?? }); // 访问 /1,2,3/true 将返回 ['ids' => ['1', '2', '3'], 'is_update' => true] $link->route('/{ids}/{is_update}', function (array $ids, bool $is_update) { return [ 'ids' => $ids, 'is_update' => $is_update ]; }); $link->start();
Request
在定义控制器方法参数类型为 Request 时,系统将会自动构造实例注入
<?php $link = new \kicoe\core\Link(); $link->route('/tag/{tag_id}', function (\kicoe\core\Request $request) { $request->input('name'); ... }, 'put'); $link->start();
Response
同上,系统也将自动构造注入 Response 类:
<?php $link = new \kicoe\core\Link(); $link->route('/tag/{tag_id}', function (\kicoe\core\Response $response, int $tag_id) { if (!$tag = search($tag_id)) { return $response->status(404); } ... return $response->json($tag); }, 'get'); $link->start();
自定义请求与返回类
继承系统 Request 和 Response 类中定义的所有公共属性将自动解析成相应实现
类似于 java 开发中的
DTO
与VO
<?php $link = new \kicoe\core\Link(); class CommentRequest extends \kicoe\core\Request { // 没有默认值的属性将作为必要参数 public int $to_id = 0; public string $name; public string $email; public string $link = ''; public string $content; /** * @return string error 错误信息 */ public function filter():string { $this->name = htmlspecialchars($this->name); $this->email = htmlspecialchars($this->email); $this->link = htmlspecialchars($this->link); $this->content = htmlspecialchars($this->content); if (!preg_match( '/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,})$/', $this->email )) { return 'email 格式错误'; } return ''; } } class ApiResponse extends \kicoe\core\Response { public int $code = 200; public string $message = ''; /** @var Comment[] */ public array $data = []; public function setBodyStatus(int $code, string $message):self { $this->code = $code; $this->message = $message; return $this; } } // 自动注入Request和Response对象 $link->route('/{aid}', function (CommentRequest $request, ApiResponse $response, int $aid) { if ($err = $request->filter()) { return $response->setBodyStatus(422, 'ValidationError: '.$err); } Comment::insert([ 'art_id' => $aid, ... // merge request data ]); $response->data = Comment::where('art_id', $aid)->get(); // {"code":200,"message":"","data":[{...}]} return $response; }, 'post'); try { $link->start(); } catch (Exception $e) { // 根据实际注入的Response来处理异常 $response = Link::make(\kicoe\core\Response); if ($response instanceof ApiResponse) { $response->setBodyStatus(500, $e->getMessage())->send(); } }
DB
使用 DB
前必须在 config 中配置 mysql
$link = new Link([ 'mysql' => [ 'db' => 'test', 'host' => 'mysql', 'port' => 3306, 'user' => 'root', 'passwd' => '123456', 'charset' => 'utf8mb4', ] ]); // 可以直接执行 sql DB::select('select id from article where id in (?) and status = ?', [1, 2, 3], 2); DB::insert('insert into article(title, status) values (?,?), (?)', 'first', 1, ['second', 2]); ...
\kicoe\core\DB::table('xx')
返回的是一个 Model
对象,该对象可以通过静态/非静态的方式调用下列方法,并返回自身对象或查询结果。
/** * Class Model * @package kicoe\core * @method array select(...$columns) * @method self where(string $segment, ...$params) * @method self orWhere(string $segment, ...$params) * @method self orderBy(...$params) * @method self limit(...$params) * @method self join(...$params) * @method self leftJoin(...$params) * @method self rightJoin(...$params) * @method self having(...$params) * @method self columns(...$params) * @method self addColumns(...$params) * @method self removeColumns(...$params) * @method self from(string $table) * @method array get() * @method self first() * @method self groupBy(string $segment) * @method int save() * @method int count() * @method int delete() * @method int update(array $data) * @method static int insert(...$data) * @method static static fetchById($id) */ class Model
Select
DB::table('tag')->where('id in (?)', [1, 2])->selete('id', 'name');
DB::table('tag')->where('color', 'aqua') ->where('deleted_at is null') ->orderBy('id', 'desc') ->limit(0, 10) ->get();
查询结果都为单个 Model
对象或 Model
对象的数组。
Insert
DB::table('tag')->insert(['name' => 'php', ...], ['name' => 'golang', ...]);
Update
DB::table('tag')->where('id', 12)->update(['name' => 'php', ...]);
Delete
DB::table('tag')->where('id', 12)->delete();
Transaction
$title = '123'; $tag_id = 10; DB::transaction(function () use ($title, $tag_id) { $article = new Article(); $article->title = $title; $article->save(); DB::table('article_tag')->insert([ 'art_id' => $article->id, 'tag_id' => $tag_id, ]); // throw any Exception tigger DB::rollBack() });
Model
<?php namespace app\model; use kicoe\core\Model; class Article extends Model { // 默认类名小写 const TABLE = 'article'; // 默认'id' const PRIMARY_KEY = 'id'; public int $id; public string $title; public int $status; public string $image; public string $summary; public string $content; public string $updated_time; public string $created_time; // public ?string $deleted_at; protected array $tags; const STATUS_DRAFT = 1; const STATUS_PUBLISH = 2; ... }
继承了 Model
的类用法和以上 DB::table('_table_name')
一样,并且会自动将其中定义所有的 public 属性作为查询字段。
use app\model\Article; // Article 对象 $art = Article::fetchById(1); $art = new Article(); $art->title = 'new blog'; // int rowCount $art->save(); // int echo $art->id; $arts = Article::where('status', Article::STATUS_PUBLISH) ->where('deleted_at is null') ->where('id in (?)', [1, 2, 3]) ->orderBy('created_time', 'desc') ->limit(0, 10); // int where 条件下的总数 $count = $arts->count(); // array Article[] $article_list = $arts->get();
增删改等操作也等同于 DB::table('table_name')
,但要注意对象的 save()
$articles = Article::get(); foreach ($articles as $article) { $article->title = '12'; $article->save(); }
以上代码执行 sql 过多是一个问题,还有就是框架中用来判断 Model
是否更新的原字段信息存在一个不是用构造函数初始化的属性中(所谓延迟),单纯的 PDO::fetchAll()
无法初始化这个属性,导致更新 sql 语句里会带上所有不为 uninitialized 的字段。
虽然可能是设计缺陷,最好还是转成以下更常规的更新方式:
Article::update(['title' => '12']);
当然不是数组的查询结果完全没问题:
$article = Article::first(); $article->title = '12'; // update article set title = ? where id = ? limit ? ["12", 2, 1] $article->save(); // 再 save 一遍不会执行任何语句 $article->save();
更多用法可以参照 blog