illogical / lava
PHP 微型框架
Requires
- php: >=5.4.0
- ext-json: *
README
微型框架
要求:PHP 5.4+, 7+, 8+
安装
如果你使用 Composer,运行以下命令
composer require illogical/lava
或者你可以 下载存档
用法
使用 Lava\App 类
use Lava\App;
如果你下载了存档,使用自动加载器
require_once '.../Lava/Autoloader.php'; $al = new Lava\Autoloader; $al->register();
环境
Lava\App::conf([data]) : 访问器
配置
App::conf([ 'charset' => 'utf-8', // encoding for HTTP headers 'type' => 'html', // default type 'home' => '/path-to-home', // home folder 'pub' => '/pub-uri', // public folder 'safe' => [ // default values 'algo' => 'md5', // hashing algorithm 'sign' => '', // signature 'salt' => '0123456789abcdef', // salt character set ], 'storage' => [ // default storage, named "0" [ 'driver' => 'PDO', 'dsn' => 'mysql:.../mysqld.sock;dbname=test;', 'username' => 'root', 'password' => NULL, ], 'db2' => [...], ... ], ]); echo App::conf()->charset; # utf-8
Lava\App::env() : 访问器
环境
echo App::env()->method; # GET var_export(App::env()->accept()); # array (0 => 'text/html', 1 => '*/*')
Lava\App::args() : 访问器
变量
值优先级:自定义、POST、GET
// URL: http://example.com/sandbox/?foo=3&bar=4&foo=5 echo App::args()->foo; # 5 var_export(App::args()->foo()); # array (0 => '3', 1 => '5')
Lava\App::cookie() : 访问器
Cookie
过期偏移量
- s - 秒
- m - 分钟
- h - 小时
- D - 天
- W - 周
- M - 月
- Y - 年
// setting App::cookie()->foo = 'bar'; App::cookie()->bar = [1, 2, 3]; // read echo App::cookie()->foo; # bar var_export(App::cookie()->bar()); # array (0 => '1', 1 => '2', 2 => '3') // additional parameters: expire, path, domain, secure App::cookie()->foo('bar', '1M'); // expire = 1 month
Lava\App::host([scheme [, subdomain]]) : 主机
返回主机名称
如果 scheme
为 TRUE,则返回当前方案
echo App::host(); # host echo App::host(TRUE); # http://host echo App::host('ftp'); # ftp://host
Lava\App::home([node, ...]) : 主页
返回主页文件夹
如果未在配置中设置,则返回脚本运行的当前文件夹
echo App::home(); # /path-to-home echo App::home('foo', 'bar'); # /path-to-home/foo/bar
Lava\App::pub([node, ...]) : 公共
返回公共文件夹
如果未在配置中设置,则返回当前文件夹
echo App::pub(); # /pub-uri echo App::pub('foo', 'bar'); # /pub-uri/foo/bar
Lava\App::uri([path|route [, data [, append]]]) : uri
返回 URI
data
中的变量将作为查询字符串附加
append
标志添加当前查询字符串
// URL: http://example.com/sandbox/?zzz=456 echo App::uri(); # /sandbox/ echo App::uri('foo', ['bar' => 123]); # /sandbox/foo?bar=123 echo App::uri('/foo', 'bar=123', TRUE); # /foo?zzz=456&bar=123
Lava\App::url([path|route [, data [, append]]]) : url
返回 URL
// URL: http://example.com/sandbox/?zzz=456 echo App::url(); # http://example.com/sandbox/ echo App::url('foo', ['bar' => 123]); # http://example.com/sandbox/foo?bar=123 echo App::url('/foo', 'bar=123', TRUE); # http://example.com/foo?zzz=456&bar=123
路由
Lava\App::route([rule [, cond]]) : 路由
占位符 :name
对应于完整的片段 ([^\/]+)
占位符 #name
匹配名称 ([^\/]+?)(?:\.\w*)?
占位符 *name
匹配剩余部分 (.+)
你可以在额外的 cond
条件中添加对环境变量的限制
如果规则不以斜杠开头,它将被附加到公共文件夹 Lava\App::pub()
// URL: http://example.com/foo1.bar/foo2.bar/foo3.bar/foo4.bar App ::route('/:node1/#node2/*node3') ->to (function($node1, $node2, $node3) { // handler echo $node1; # foo1.bar echo $node2; # foo2 echo $node3; # foo3.bar/foo4.bar }); // route search App::routes_match(); // environment constraint App::route('/foo', [ 'method' => ['GET', 'HEAD'], // if the method is GET or HEAD 'user_addr' => '127.0.0.1', // and the user is local 'user_agent' => '/^Mozilla/', // and the browser is Mozilla ]); // method restriction only App::route('/foo', 'DELETE');
Lava\App::route_get([rule]) : 路由
限制路由为 GET 方法
App::route_get ('/foo'); // analog App::route ('/foo', 'GET');
Lava\App::route_post([rule]) : 路由
App::route_post('/foo');
Lava\App::routes_match() : 结果
执行匹配路由的处理程序
如果处理程序返回 TRUE
,则继续检查其余路由,否则停止检查并返回处理程序的结果
App::routes_match();
route->cond(cond) : 路由
向路由添加环境约束
App ::route('/foo') ->cond (['user_addr' => '/^192\.168\./']);
route->name(name) : 路由
用于将路由转换为路径
// URL: http://example.com/foo/123 App ::route('/foo/#id') ->name ('route_name') ->to (function($id) { echo App::uri('route_name', ['id' => $id + 1]); # /foo/124 });
route->to(mixed) : 路由
路由处理程序
默认方法 Lava\App::env()->method
// function App::route('/foo')->to(function() {echo 'hello';}); // class|namespace, method App::route('/foo')->to('Controller\Foo', 'bar'); // file, method // the class name must match the file name // an instance of the Foo class will be created and the bar method will be called App::route('/foo')->to('controller/Foo.php', 'bar'); // file, class|namespace, method App::route('/foo')->to('controller/Foo.php', 'Ctrl\Foo', 'bar'); // if the class is different from the file name or if we need to specify a namespace
渲染
Lava\App::render(handlers) : has_handler
使用类型 Lava\App::type()
执行处理程序,如果不存在,则使用索引 0
如果请求的数据类型不存在,则使用 Lava\App::conf()->type
如果类型是 json
并且存在 Lava\App::args()->callback
值,则返回 JSONP
App::route('/page')->to(function() { App::render([ 'html' => 'HTML CONTENT', 'json' => ['bar' => 123], function () { echo 'OTHER TYPE: ' . App::type(); }, ]); }); // URL: http://example.com/page.html App::routes_match(); # HTML CONTENT // URL: http://example.com/page.json App::routes_match(); # {"bar":123} // URL: http://example.com/page.xml App::routes_match(); # OTHER TYPE: xml // if Lava\App::conf()->type == 'html' // URL: http://example.com/page App::routes_match(); # HTML CONTENT
Lava\App::redirect([url|uri|route [, data [, append]]]) : void
附加到 Location
标头
App::redirect('/foo');
安全
Lava\App::safe()->uuid() : uuid
返回 UUID
你可以在配置中指定散列算法,默认是 md5
echo App::safe()->uuid(); # 055fb982653fef1ae76bde78b10f7221
Lava\App::safe()->uuid_signed() : [signed_uuid, uuid]
返回签名 UUID
你可以在配置中指定签名,默认为空字符串
list($signed, $uuid) = App::safe()->uuid_signed(); echo $signed; # 31bd185d9b3929eb56ae6e4712b73962dcd6b2b55b5287117b9d65380f4146e3 echo $uuid; # 31bd185d9b3929eb56ae6e4712b73962
Lava\App::safe()->check(signed_uuid) : uuid
检查签名 UUID
echo App::safe()->check($signed); # 31bd185d9b3929eb56ae6e4712b73962
Lava\App::safe()->salt(size) : random_string
返回指定长度的随机字符串
您可以在配置中更改可用的字符列表,默认为 0123456789abcdef
echo App::safe()->salt(16); # f8da4f571ec3de9d
验证
Lava\App::is_valid(val, tests) : bool_result
测试
-
tinyint[:unsigned]
-
smallint[:unsigned]
-
mediumint[:unsigned]
-
integer[:unsigned]
-
bigint[:unsigned]
-
numeric[:precision[:scale]]
-
boolean
-
string[:min_size[:max_size]]
-
char[:size]
-
email
-
url
-
ipv4
-
date
-
time
-
datetime
-
lt[:num]
-
lte[:num]
-
gt[:num]
-
gte[:num]
-
bool
-
array
-
regexp
-
function
// the string is between 1 and 20 characters and matches Email echo App::is_valid('me@example.com', ['string:1:20', 'email']); # TRUE
Storage\PDO
Lava\Storage::source('PDO', opts) : storage
创建
$storage = Lava\Storage::source('PDO', [ 'dsn' => 'mysql:unix_socket=...mysqld.sock;dbname=name', 'username' => 'root', 'password' => '', ]);
storage->exec(query[, bind]) : row_count
运行 SQL 查询并返回其执行的行数
$storage->exec('DELETE FROM users'); $storage->exec( 'INSERT INTO users (login, email) VALUES (?, ?)', ['username', 'abc@mail'] ); $storage->exec( 'INSERT INTO users (login, email) VALUES (:login, :email)', ['login' => 'username', 'email' => 'abc@mail'] );
storage->fetch(query[, bind]) : row
从结果集中获取一行
$user = $storage->fetch('SELECT * FROM users WHERE id = ?', 123);
storage->fetch_all(query[, bind[, index]]) : rows
从结果集中获取所有行
index
用于指定将成为索引的字段名称
$users = $storage->fetch_all('SELECT * FROM users');
storage->last_insert_id() : id
最后插入行的 ID
$id = $storage->last_insert_id();
storage->error() : error_info
由驱动程序定义的错误消息
$error = $storage->error();
storage->factory([target]) : factory
查询工厂
factory->get([index]) : rows
数据选择
// index data with id value $data = $storage->factory('users')->get('id'); # query: SELECT * FROM `users`
factory->one() : row
选择一条记录
$data = $storage->factory('users')->one(); # query: SELECT * FROM `users` LIMIT ? # bind: 1
factory->columns(expression) : factory
列或计算
$data = $storage ->factory('users') // columns(expression) ->columns('id') // columns([alias => expression, ...]) ->columns(['full_name' => 'CONCAT(first_name, " ", last_name)']) ->get(); # query: SELECT `id`, CONCAT(first_name, " ", last_name) AS `full_name` FROM `users`
factory->*join(target, relations[, bind]) : factory
表连接
$data = $storage ->factory('users') ->join('profiles', 'id') ->left_join('sessions', 'sessions.user_id = users.id') ->right_join('roles', ['roles.user_id' => 'users.id', 'roles.id' => '?'], 123) ->get(); # query: SELECT * FROM `users` # JOIN `profiles` USING (`id`) # LEFT JOIN `sessions` ON sessions.user_id = users.id # RIGHT JOIN `roles` ON `roles`.`user_id` = `users`.`id` AND `roles`.`id` = ? # bind: 123
factory->filter*(expression) : factory
数据过滤: filter_eq
, filter_ne
, filter_lt
, filter_gt
, filter_lte
, filter_gte
, filter_like
, filter_not_like
, filter_in
, filter_not_in
, filter_between
, filter_is_null
, filter_is_not_null
, filter_raw
上下文更改: filter_and
, filter_or
, filter_not
filter
根据上下文工作,对于数据作为 filter_eq
,对于闭包作为 filter_and
$data = $storage ->factory('users') // filter(key, val) ->filter_eq('id', 123) // filter(expression) ->filter_ne(['name' => 'guest', 'email' => 'test']) ->get(); # query: SELECT * FROM `users` WHERE (`id` = ? AND (`name` != ? AND `email` != ?)) # bind: 123, 'guest', 'test' $data = $storage ->factory('users') ->filter_or(['name' => 'guest', 'email' => 'test']) ->get(); # query: SELECT * FROM `users` WHERE (`name` = ? OR `email` = ?) # bind: 'guest', 'test' $data = $storage ->factory('users') ->filter_or(function($filter) { // in a closure without the filter_ prefix $filter ->like('name', '%test%') ->and(function($filter) { $filter->gt('id', 10)->lte('id', 20); }) ->is_not_null('email'); }) ->get(); # query: SELECT * FROM `users` WHERE (`name` LIKE ? OR (`id` > ? AND `id` <= ?) OR `email` IS NOT ? # bind: '%test%', 10, 20, NULL $data = $storage ->factory('users') ->filter_not(function($filter) { $filter ->or(['name' => 'guest', 'email' => 'test']) ->in('role_id', [2, 4, 6]); }) ->get(); # query: SELECT * FROM `users` WHERE NOT ((`name` = ? OR `email` = ?) AND `role_id` IN (?, ?, ?)) # bind: 'guest', 'test', 2, 4, 6 // subqueries $data = $storage ->factory('users') ->filter_in('id', $storage->factory('sessions')->columns('user_id')->filter('active', 1)) ->get(); # query: SELECT * FROM `users` WHERE `id` IN (SELECT `user_id` FROM `sessions` WHERE `active` = ?) # bind: 1
factory->group_by(expression) : factory
分组
$data = $storage ->factory('users') ->columns(['role_id', 'count' => 'COUNT(*)']) ->group_by('role_id') ->get(); # query: SELECT `role_id`, COUNT(*) AS `count` FROM `users` GROUP BY `role_id`
factory->having*(expression) : factory
类似于 filter
过滤数据: having_eq
, having_ne
, having_lt
, having_gt
, having_lte
, having_gte
, having_like
, having_not_like
, having_in
, having_not_in
, having_between
, having_is_null
, having_is_not_null
, having_raw
上下文更改: having_and
, having_or
, having_not
having
根据上下文工作,对于数据作为 having_eq
,对于闭包作为 having_and
$data = $storage ->factory('users') ->columns(['role_id', 'count' => 'COUNT(*)']) ->group_by('role_id') ->having_or(function($having) { $having->gt('count', 2)->lte('count', 5); }) ->get(); # query: SELECT `role_id`, COUNT(*) AS `count` FROM `users` GROUP BY `role_id` HAVING (`count` > ? OR `count` <= ?) # bind: 2, 5
factory->order_by*(expression) : factory
排序
$data = $storage ->factory('users') ->order_by('name') ->order_by_desc('email') ->get(); # query: SELECT * FROM `users` ORDER BY `name` ASC, `email` DESC
factory->limit(count[, offset]) : factory
限制返回的记录数
$data = $storage ->factory('users') ->limit(10) ->get(); # query: SELECT * FROM `users` LIMIT ? # bind: 10
factory->add(data[, update]) : row_count
插入数据
$users = $storage->factory('users'); $users->add([ 'login' => 'username', 'email' => 'abc@mail', ]); # query: INSERT INTO `users` SET `login` = ?, `email` = ? # bind: 'username', 'abc@mail' $archive = $storage->factory('archive_users'); $users->add($archive); # query: INSERT INTO `users` SELECT * FROM `archive_users` $users->columns(['login', 'email'])->add( $archive->columns(['username', 'email'])->filter_lt('id', 123) ); # query: INSERT INTO `users` (`login`, `email`) # SELECT `username`, `email` FROM `archive_users` WHERE `id` < ? # bind: 123 $storage ->factory('log') ->add(['name' => 'access', 'count' => 1], ['count = count + 1']); # query: INSERT INTO `log` SET `name` = ?, `count` = ? # ON DUPLICATE KEY UPDATE count = count + 1 # bind: 'access', 1
factory->set(data) : row_count
更新数据
$data = $storage ->factory('users') ->filter('id', 123) ->set(['name' => 'bar']); # query: UPDATE `users` SET `name` = ? WHERE `id` = ? # bind: 'bar', 123
factory->del() : row_count
删除数据
$data = $storage ->factory('users') ->filter('id', 123) ->del(); # query: DELETE FROM `users` WHERE `id` = ? # bind: 123
factory->count([key]) : count
返回表达式数量
$data = $storage ->factory('users') ->filter_gt('id', 123) ->count('email'); # query: SELECT COUNT(`email`) AS `val` FROM `users` WHERE `id` > ? # bind: 123
factory->min(key) : value
返回最小值
$data = $storage ->factory('users') ->filter_like('email', '%@mail%') ->min('id'); # query: SELECT MIN(`id`) AS `val` FROM `users` WHERE `email` LIKE ? # bind: '%@mail%'
factory->max(key) : value
返回最大值
$data = $storage ->factory('users') ->filter_in('role_id', [2, 3]) ->max('id'); # query: SELECT MAX(`id`) AS `val` FROM `users` WHERE `role_id` IN (?, ?) # bind: 2, 3
factory->avg(key) : value
返回平均值
$data = $storage ->factory('users') ->avg('id'); # query: SELECT AVG(`id`) AS `val` FROM `users`
factory->sum(key) : value
返回值的总和
$data = $storage ->factory('users') ->sum('id'); # query: SELECT SUM(`id`) AS `val` FROM `users`
Model\SQL
use Lava\Model\SQL; class User extends SQL { // meta data protected static // optionally, storage name // name from Lava\App::conf()->storage() // default is "0" $storage = '0', // optionally, table name // defaults to a class name in snake case with an "s" at the end $table = 'users', // optionally, the name of the primary key // defaults to "id" $id = 'id', // columns description $columns = [ 'key' => [ // value cannot be NULL 'not_null' => FALSE|TRUE, // value is unique 'unique' => FALSE|TRUE, // default value 'default' => VALUE, // validation tests 'valid' => [tests], ], ... ], // optionally, default limit // if not specified at selection // unrestricted by default $limit = 0; }
创建
$user = new User; $user->name = 'foo'; $user->email = 'mail@example.com'; $user->save();
选择
// selecting one object by ID $user = User::one(123); echo $user->name; // selecting a collection by condition $users = User::find(['active' => TRUE])->get(); foreach ($users as $user) { echo $user->name; }
默认值
class Session extends SQL { // method name must start with the prefix "column_default_" public static function column_default_created () { return date('Y-m-d H:i:s'); } } $session = new Session; echo $session->created; // now
关系
class User extends SQL { public function sessions () { return $this ->has_many(Session::classname()); } // additional filters public function active_sessions () { return $this ->sessions() ->filter(['active' => TRUE]); } } class Session extends SQL { public function user () { return $this->belongs_to(User::classname()); } } $user = User::one(123); // property returns a collection foreach ($user->sessions as $session) { echo $session->id; } // method returns resultset $active = $user ->sessions() ->filter(['active' => TRUE]) ->get(); // similarly $active = $user ->active_sessions;
导入 & 导出
class Session extends SQL { public static function import ($data) { if (isset($data['ip'])) { $data['ip'] = long2ip($data['ip']); } return $data; } public static function export ($data) { if (isset($data['ip'])) { $data['ip'] = ip2long($data['ip']); } return $data; } }