remcosmits / backend-framework
轻量级PHP框架。包括快速安全的数据库QueryBuilder、具有关系的模型、高级路由(动态路由、中间件、分组、前缀、命名路由)。
Requires
- opis/closure: ^3.6
- php-curl-class/php-curl-class: ^9.5
Requires (Dev)
- phpstan/phpstan: ^1.2
- phpunit/phpunit: ^9
- dev-master
- v2.7.5
- v2.7.4
- V2.7.3
- v2.7.2
- v2.7.1
- v2.7
- v2.6.9.1
- v2.6.9
- v2.6.8
- v2.6.7
- v2.6.6
- v2.6.5
- v2.6.4
- v2.6.3
- v2.6.2
- v2.6.1
- v2.6
- v2.5.9.3
- v2.5.9.2
- v2.5.9.1
- v2.5.9
- v2.5.8
- v2.5.7
- v2.5.6
- v2.5.5
- v2.5.4
- v2.5.3
- v2.5.2
- v2.5.1
- v2.5
- v2.4.9.4
- v2.4.9.3
- v2.4.9.2
- v2.4.9.1
- v2.4.9
- v2.4.8.5
- v2.4.8.4
- v2.4.8.3
- v2.4.8.2
- v2.4.8.1
- v2.4.8
- v2.4.7.3
- v2.4.7.2
- v2.4.7
- v2.4.6
- v2.4.5
- v2.4.4
- v2.4.3
- v2.4.2
- v2.4.1
- v2.4
- v2.3.1
- v2.3
- v2.2.2
- v2.2.1
- v2.2
- v2.1
- V2
- V1
- dev-analysis-9mEpRl
- dev-add-route-model-binding-support
- dev-RemcoSmitsDev-readme-documentation
- dev-database-schema-support
- dev-support-eloquent-relations
This package is auto-updated.
Last update: 2023-08-13 20:29:03 UTC
README
轻量级PHP后端框架。包括快速安全的数据库QueryBuilder,高级路由(具有动态路由(中间件,分组,前缀,命名路由)
要设置此PHP后端框架,您需要使用composer安装此软件包
安装
composer require remcosmits/backend-framework
设置
// namespace for App/Route class use Framework\Http\Route\Route; use Framework\App; // include autoloader(composer) require_once(__DIR__.'/../vendor/autoload.php'); // make instance of App $app = new App(); // start app load all default(security) settings $app->start(); // when you want to make a singleton you can do that like: $app->setInstance( new Route(), new ClassThatYouWant() ); // When you want to use the instance you can do that like this: /** @var Route */ 1. $app->getInstance(Route::class); 2. app()->getInstance(Route::class); 3. app(Route::class); // make instance of Route class $route = new Route(); <!-- all routes --> // init all routes and check witch route is equals to the current uri $route->init();
路由
基本路由
支持的请求方法:GET
、POST
、PUT
、DELETE
、PATCH
// supported request types/methods: $route->get(); $route->post(); $route->put(); $route->patch(); $route->delete(); // route using callback function $route->get('/account', function () { echo "Account page"; }); // route using class methods $route->get('/account', [AccountController::class,'index']); // route using multi request methods(suports all requests methods) $route->match('GET|POST','/user/{userID}', function ($userID) { echo "user information"; });
动态路由
对于动态路由,您可以使用pattern
方法允许动态参数仅具有特定值。您可以在每个路由方法操作GET
、POST
、PUT
、DELETE
、PATCH
或match
方法(允许多种请求方法)上执行此操作。您甚至可以在嵌套路由中覆盖它们。
// route using dynamic routing(params) // all params can be accessed with the given name $route->get('/account/{accountID}', function ($accountID) { echo "AccountID: {$accountID}"; }); // You can also change the regex pattern of the dynamic params // Now accountID can be only an number. $route->get('/account/{accountID}', function ($accountID) { echo "AccountID: {$accountID}"; })->pattern(['accountID' => '[0-9]+']); // dynamic route prefix $route->prefix('account')->group(function (Route $route) { // group by dynamic prefix param $route->prefix('/{accountID}')->group(function (Route $route) { // Route will be: /account/{accountID}/profile $route->get('/profile', function($accountID){ echo "Account profile page {$accountID}"; })->pattern(['accountID' => '[0-9]+']); // you can set an other pattern for `{accountID}` // route will be: /account/{accountID}/profile $route->get('/profile', function($accountID){ echo "Account profile page {$accountID}"; })->pattern(['accountID' => '[0-9]+\-[a-z]+']); }); // Route will be: /account $route->get('/', function(){ echo "Account page"; }); });
命名路由
当您想通过名称获取路由时,可以使用getRouteByName
方法,或使用redirect()->route()
。当您想要访问动态命名路由时,您需要通过其键传递给定的值。
// show all accounts route()->getRouteByName('account.index'); // Thhis will get a single route route()->getRouteByName('account.show', ['accountID' => 1]); // This will redirect you to the route redirect()->route('account.index'); // This will redirect you to the route with dynamic param redirect()->route('account.show', ['accountID' => 1]);
路由前缀
前缀可用于防止编写长的路由URI,并分组所有具有相同URI部分的路线。您可以在单个路由或分组之前使用此方法。前缀还可以包含动态路由。然后,您可以在每个请求方法GET
、POST
、PUT
、DELETE
、PATCH
或match
方法(允许多种请求方法)上指定模式。
// Route will end up like: /account/profile $route->prefix('account')->get('/profile', function () { echo "Account profile page"; }); // Grouped routes with prefix $route->prefix('account')->group(function (Route $route) { // when pattern was not correct // Route will end up like: /account/{accountID} $route->get('/{accountID}', function ($accountID) { // Get accountID from URL echo "AccountID: {$accountID}"; }); }); // Grouped routes with dynamic prefix $route->prefix('account')->group(function (Route $route) { // dynamic route prefix $route->prefix('{accountID}')->group(function(Route $route){ // Route will end up like: /account/{accountID}/profile $route->get('/profile', function ($accountID) { // Get accountID from URL echo "AccountID: {$accountID}"; })->pattern(['accountID' => '[0-9]+']); // you can change the dynamic route prefix pattern after all (get, post, put, delete, patch) methods }); });
路由中间件
中间件可用于防止对请求方法GET
、POST
、PUT
、DELETE
、PATCH
或match
方法(允许多种请求方法)进行访问
// Route with single middleware check $route->middleware(false)->get('/profile', function () { echo "Account profile page"; }); // Route with array of middleware checks $route->middleware([true, false])->get('/profile', function () { echo "Account profile page"; }); // OR $route->middleware(true, false)->get('/profile', function () { echo "Account profile page"; }); // Route with custom validate class $route->middleware(true, CustomMiddlewareClass::class)->get('/profile', function () { echo "Account profile page"; }); // The class shout like this: class CustomMiddlewareClass { public function handle(array $route, Closure $next){ // return false when middleware need to fail if(true !== false){ return false; } // when middleware is successful return $next(); } }
路由分组
分组路由可以在您有中间件/前缀需要应用于多个路由时非常有用。
// Grouped routes with prefix $route->prefix('account')->group(function (Route $route) { // when pattern was not correct // Route will end up like: /account/{accountID} $route->get('/{accountID}', function ($accountID) { // Get accountID from URL echo "AccountID: {$accountID}"; }); }); // Grouped routes with middleware check $route->middleware(true)->group(function (Route $route) { // when pattern was not correct // Route will end up like: /account/{accountID} $route->get('/{accountID}', function ($accountID) { // Get accountID from URL echo "AccountID: {$accountID}"; }); });
请求
请求方法
request()->all()
将从GET
、POST
(php://input)、FILES
获取所有请求信息
request()->all();
request()->get()
将从GET
获取所有请求信息
$_GET['name'] = 'test'; request()->get('name'); // test
request()->post()
将从POST
(php://input)获取所有请求信息
$_POST['name'] = 'test'; request()->post('name'); // test
request()->file()
将从FILES
获取所有请求信息
$_FILES['name'] = []; // showing purpose(invalid file array) request()->file('name'); // will get file array
request()->cookies()
将从request
|server
获取所有cookies
$_SERVER['Cookie'] = 'PHPSESSID=u30vn0lgpmf6010ro4ol9snle1; name=test'; request()->cookies('name'); // test
request()->server()
将从SERVER
获取所有服务器头信息
$_SERVER['HTTP_METHOD'] = 'GET'; request()->server('HTTP_METHOD'); // GET
request()->headers()
将从getallheaders
|SERVER
获取所有请求头信息
header('Content-Type: application/json;'); request()->headers('Content-Type'); // application/json
请求验证方法
要验证请求输入,请使用request()->validate()
// Rules: // - string // - int // - float // - array // - min:_NUMBER_ // - max:_NUMBER_ // - regex:_REGEX_ //without / before and after // - email // - url // - ip // - YourCustomRuleClass::class // that needs to extend `CustomRule` and must have the `validate` method $_GET['test'] = ''; // this will fail (min:1) $validated = request()->validate([ 'test' => ['required', 'string', 'min:1', 'max:255'] ]); if($validated->failed()){ // do action $messages = $validated->getErrorMessages(); // get error messages $failedRules = $validated->getFailedRules(); } // get validated data $validatedData = $validated->getData();
自定义验证规则
class YourCustomRuleClass extends CustomRule { public function validate(mixed $value): bool { // check if is valid if($value === 'test'){ return true; } // This message will be combined with the customrule $this->message('Your value must be test'); return false; } }
request()->csrf()
生成一个csrf令牌,您可以使用request()->validateCsrf()
来验证。您应该在不是GET
请求的每个后端请求中使用此功能。
<input type="hidden" name="_token" value="<?= request()->csrf(); ?>">
request()->validateCsrf()
将验证您的csrf令牌
是否有效。
if(!request()->validateCsrf()){ throw new \Exception('Your token is not valid!'); }
响应
响应类/辅助工具可以帮助您发送带有正确头信息和信息的响应。在发送响应时,它将自动选择正确的Content-Type
。默认的responsecode
是200
,信息为OK
。每个响应代码都包含其自己的信息,将自动包含。您可以将所有方法链接起来,最后的方法链将返回响应。
方法
response()->json()
将所有信息转换为json
response()->json(['this is a array to json']); // headers Content-Type: application/json; charset=UTF-8; HTTP/1.1 200 OK
response()->text()
允许text/html
response()->text('this is a normal string|html'); // headers Content-Type: text/html; charset=UTF-8; HTTP/1.1 200 OK
response()->code()
将设置响应码,它底层使用http_response_code
response()->code(404); // headers Content-Type: text/html; charset=UTF-8; HTTP/1.1 404 Not Found
response()->headers()
将在响应中附加头信息
response()->headers(['Test' => 'test']); // headers Test: test Content-Type: text/html; charset=UTF-8; HTTP/1.1 200 OK
response()->exit()
在发送响应后将使用php的exit
函数
response()->json(['message' => 'Something went wrong'])->exit(); // headers Content-Type: Application/json; charset=UTF-8; HTTP/1.1 200 OK
response()->view()
将视图(内容文件)附加到响应
response()->view('index',['userIds' => [1,2,3,4]]); // headers Content-Type: text/html; charset=UTF-8; HTTP/1.1 200 OK
查询构建器
方法
logSql()
在页面或ray内部记录查询+绑定
$db->table('users')->logSql()->where('id', '=', 1);
raw(query: string, bindings: array)
当您想使用用户输入值时,您可能希望使用绑定参数
// without bindings $db->raw('SELECT * FROM `users` WHERE `users`.`id` = 1'); // with bindings $db->raw('SELECT * FROM `users` WHERE `users`.`id` = ?', [1]);
table(table: string, columns: ...string|array)
在页面或ray内部记录查询+绑定
// SELECT * FROM `users` $db->table('users')->all(); // SELECT * FROM `users` LIMIT 1 OFFSET 0 $db->table('users')->one(); // DELETE FROM `users` $db->table('users')->delete(); // UPDATE `users` SET ... $db->table('users')->update(['name' => 'test name']);
select(...string|array)
将设置选择列,子查询
$db->table('users', 'id')->where('id', '=', 1); $db->table('users', 'id', 'email')->where('id', '=', 1); $db->table('users', ['id', 'email'])->where('id', '=', 1); // OR $db->table('users')->select('id')->where('id', '=', 1); $db->table('users')->select('id', 'email')->where('id', '=', 1); $db->table('users')->select(['id', 'email'])->where('id', '=', 1); // OR sub select // SELECT (SELECT count(posts.id) FROM posts WHERE users.id = posts.user_id) as post_count FROM `users` $db->table('users')->select([ 'post_count' => function(QueryBuilder $query){ $query->table('posts', 'count(posts.id)')->whereColumn('users.id', '=', 'posts.user_id'); } ]);
where(column: Closure|string, operator: array|string, value: mixed = null, boolean: string(OR|AND) = 'AND')
附加where语句
// SELECT * FROM `users` WHERE `email` = ? // bindings: ['test@example.com'] $db->table('users')->where('email', '=', 'test@example.com'); $db->table('users')->where('email', 'test@example.com'); // SELECT * FROM `users` WHERE `email` = ? OR `email` = ? // bindings: ['test@example.com', 'test@example.com'] $db->table('users')->where('email', '=', 'test@example.com')->where('email', '=', 'test@example.com', 'OR'); // SELECT * FROM `users` WHERE `email` = ? AND `email` = ? // bindings: ['test@example.com', 'test@example.com'] $db->table('users')->where('email', '=', 'test@example.com')->where('email', '=', 'test@example.com', 'AND');
whereRaw(query: string|closure, bindData: array = [], boolean: string(OR|AND) = 'AND')
// SELECT * FROM `users` WHERE `users`.`email` LIKE '%test@example.com%' $db->table('users')->whereRaw('`users`.`email` LIKE %test@example.com%'); // SELECT * FROM `users` WHERE `users`.`email` LIKE ? // bindings: ['test@example.com'] $db->table('users')->whereRaw('`users`.`email` LIKE ?', ['test@example.com']); // SELECT * FROM `users` WHERE `users`.`email` LIKE ? AND `users`.`email` LIKE ? // bindings: ['test@example.com', 'test@example.com'] $db->table('users')->whereRaw('`users`.`email` LIKE ?', ['test@example.com'])->whereRaw('`users`.`email` LIKE ?', ['test@example.com'], 'AND');
orWhere(column: Closure|string, operator: string|null, value: mixed)
Eloquent版本where('column', 'operator', 'value', 'OR')
$db->table('users')->where('users.id', '=', 1)->orWhere('users.email', '=', 'test@example.com');
whereIn(column: string, value: Closure|array, boolean: string(OR|AND) = 'AND')
// SELECT * FROM `users` WHERE `id` IN (?) // bindings: ['1,2,3,4'] $db->table('users')->whereIn('id', [1,2,3,4]); // SELECT * FROM `users` WHERE `id` IN (SELECT `user_id` FROM posts) $db->table('users')->whereIn('id', function(QueryBuilder $query){ $query->table('posts', 'user_id'); });
whereExists(callback: Closure, boolean: string(OR|AND) = 'AND', not: boolean = false)
// SELECT * FROM `users` WHERE EXISTS (SELECT `created_at` FROM `posts` WHERE `created_at` > ? AND `users`.`id` = `posts`.`user_id` LIMIT 1 OFFSET 0) $db->table('users')->whereExists(function(QueryBuilder $query){ $query->table('posts', 'created_at')->where('created_at', '>', '2022-01-01') ->whereColumn('posts.id', '=', 'users.id', 'AND') ->limit(1); });
whereNotExists(callback: closure, boolean: string(OR|AND) = 'AND')
Eloquent版本whereExists(callback, 'AND', true)
// SELECT * FROM `users` WHERE NOT EXISTS (SELECT `created_at` FROM `posts` WHERE `created_at` > ? AND `users`.`id` = `posts`.`user_id` LIMIT 1 OFFSET 0) $db->table('users')->whereNotExists(function(QueryBuilder $query){ $query->table('posts', 'created_at')->where('created_at', '>', '2022-01-01') ->whereColumn('posts.id', '=', 'users.id', 'AND') ->limit(1); });
whereColumn(column: string, operator: string|null, value: string|null, boolean: string(OR|AND) = 'AND')
请确保您不要使用来自用户的原始输入,因为列将不会进行转义!
// SELECT (SELECT count(id) FROM `posts` WHERE `users`.`id` = `posts`.`user_id`) as post_count FROM `users` $db->table()->select([ 'post_count' => function(QueryBuilder $query){ $query->table('posts', 'count(id)')->whereColumn('users.id', '=', 'posts.user_id'); } ]);
join(table: string, first: Closure|string, first: string|null, operator: string|null, value: string|null, type: string(INNER|LEFT|RIGHT|CROSS) = 'INNER')
请确保您不要使用来自用户的原始输入,因为列将不会进行转义!如果您想使用用户输入的值,请确保在闭包(join)内部使用 where()
。
// SELECT * FROM `users` INNER JOIN `posts` ON `users`.`id` = `posts`.`user_id` $db->table('users')->join('posts', 'users.id', '=', 'posts.user_id'); // SELECT * FROM `users` INNER JOIN (`posts` ON `users`.`id` = `posts`.`user_id` OR `posts` ON `users`.`id` = `posts`.`user_id`) $db->table('users')->join('posts', function(JoinClause $join){ $join->on('users.id', '=', 'posts.user_id')->orOn('users.id', '=', 'posts.user_id'); }); // join with user input // SELECT * FROM `users` INNER JOIN `posts` ON `users`.`id` = ? // bindings [1] $db->table('users')->join('posts', function(JoinClause $join){ $join->where('users.id', '=', 1); });
leftJoin()
Eloquent 的 join('table', 'firstColumn', 'operator', 'secondColumn', 'LEFT')
// SELECT * FROM `users` LEFT JOIN `posts` ON `users`.`id` = `posts`.`user_id` $db->table('users')->leftJoin('posts', 'users.id', '=', 'posts.user_id');
rightJoin()
Eloquent 的 join('table', 'firstColumn', 'operator', 'secondColumn', 'RIGHT')
// SELECT * FROM `users` RIGHT JOIN `posts` ON `users`.`id` = `posts`.`user_id` $db->table('users')->rightJoin('posts', 'users.id', '=', 'posts.user_id');
limit(limit: int)
// SELECT * FROM `users` LIMIT 50 OFFSET 0 $db->table('users')->limit(50)->all([]);
offset(limit: int)
// SELECT * FROM `users` LIMIT 50 OFFSET 10 $db->table('users')->limit(50)->limit(10)->all([]);
orderBy(column: string, direction: string(ASC|DESC) = 'ASC')
$db->table('users')->orderBy('create_at')->all([]); $db->table('users')->orderBy('create_at', 'ASC')->all([]); // OR $db->table('users')->orderBy('create_at', 'DESC')->all([]);
groupBy(...string)
// SELECT * FROM `users` GROUP BY `title` $db->table('posts')->groupBy('title'); // OR // SELECT * FROM `users` GROUP BY `title`, `user_id` $db->table('posts')->groupBy('title', 'user_id');
when(when: boolean, callback: Closure)
$isAdmin = false; $db->table('posts')->when(!$isAdmin, function(QueryBuilder $query){ $query->where('user_id', '=', 2); })->all([]);
paginate(currentPage: int, perPage: int = 15)
// SELECT * FROM `users` LIMIT 50 OFFSET 0 $pagination = $db->table('users')->paginate(1, 50); // `$pagination` is structures like this: [ 'current_page' => 1, 'first_page' => 1, 'last_page' => .., 'per_page' => 50, 'total_pages' => .., // number of total pages, 'total_results' => .., // number of results found 'next_page' => [ 'exists' => true, // false when there is no next page 'page' => 2 // the next page number ], 'prev_page' => [ 'exists' => false, // false when there is no previous page 'page' => 1 // the previous page number ], 'results' => [] // array of results ]
all(fallbackReturnValue: mixed = false, fetchMode: int|null = null)
// You can use this inside a foreach without using the `all()` method $db->table('users'); // OR $db->table('users')->all(); // OR when query fails return value will be `[]` $db->table('users')->all([]); // Fetch mode(default fetch mode: \POD::FETCH_ASSOC) $db->table('users')->all([], \POD::FETCH_ASSOC | \POD::FETCH_COLUMN);
one(fallbackReturnValue: mixed = false, fetchMode: int|null = null)
// SELECT * FROM `users` LIMIT 1 OFFSET 0 $db->table('users')->one(); // when query fails return value will be `[]` $db->table('users')->one([]); // Fetch mode(default fetch mode: \POD::FETCH_ASSOC) $db->table('users')->one([], \POD::FETCH_ASSOC | \POD::FETCH_COLUMN);
column(fallbackReturnValue: mixed = false, column: int = 0)
$userInfo = $db->table('users', 'username', 'email')->limit(1); // to retrieve `username` use $username = $userInfo->column(0); // to retrieve `email` use $email = $userInfo->column(1);
insert(insertData: array<string,mixed>)
当查询 Failed
时,insert 方法将返回 false
,否则方法将返回 insertId
$insertId = $db->table('posts')->insert([ 'title' => 'test title', 'slug' => 'test-title', 'body' => 'This is an test body' ]);
update(updateData: array<string,mixed>)
根据是否影响了行返回布尔值
$passed = $db->table('users')->where('id', '=', 1)->update([ 'titel' => 'Update title' ]);
delete()
根据是否影响了行返回布尔值
$passed = $db->table('users')->where('id', '=', 1)->delete();
Model
模型将代表一个数据库表。这将允许您仅编写属于单个数据库表的代码。模型将自动尝试根据您的 模型名称 猜测 数据库表名称。当猜测不正确时,您可以通过如下方式指定表名 protected string $table = 'table_name'
默认的 主键 是 id
,您可以通过 protected string $primaryKey = 'your_primary_key'
来覆盖它
use Framework\Model\BaseModel; // table name => `posts` // primaryKey => 'id' class Post extends BaseModel { } // table name => `categories` // primaryKey => 'id' class Category extends BaseModel { } // table name => `posts` // primaryKey => 'ID' class WeirdModelName extends BaseModel { protected string $table = 'posts'; protected string $primaryKey = 'ID' }
当您进行查询时,模型 将自动指定 表名。当您想使用 QueryBuilder
时,您可以这样做:BaseModel 有一个名为 find($find: mixed, $key: string|null = null)
的方法,这是一个用于查找单个结果的简写。
use Framework\Model\BaseModel; class Post extends BaseModel { public function paginatePosts(int $currentPage = 1, int $perPage = 15): array { return $this->orderBy('created_at', 'ASC')->paginate($currentPage, $perPage); } } // Make instance of `Post` model $post = new Post(); // This finds a single result in the database based on the primary key $singlePost = $post->find(find: 'some_primary_key'); $singlePost = $post->find(find: 'some_title', key: 'title'); // This will contain all posts from the database $posts = $post->all([]); // This will get all posts with pagination that where orderd by `created_at` ASC $paginatedPosts = $post->paginatePosts(currentPage: 1, perPage: 25);