PHP 微型框架

1.2.2 2024-08-29 22:31 UTC

This package is auto-updated.

Last update: 2024-09-29 22:50:33 UTC


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;
    }
}