selikhovleonid/ nadir2
Nadir PHP 微型框架版本 2
Requires
- php: >=7.1.0
- selikhovleonid/accessors-trait: dev-master
This package is not auto-updated.
Last update: 2024-09-29 06:41:10 UTC
README
另一个 PHP 微型框架
Nadir 是一个 PHP 微型框架,可以帮助您快速编写 Web 应用程序、控制台应用程序和 RESTful 服务。它基于 MVC 模式。这个微型框架提供了广泛的修改和定制机会。
安装
Nadir2 最低要求的 PHP 版本是 PHP 7.1。您需要 Composer 依赖管理器来安装此微型框架。开始使用 Nadir2 的最简单方法是运行以下 shell 命令创建项目模板
php composer.phar create-project -s dev selikhovleonid/nadir2-skeleton <project-name>
项目结构
创建的项目模板结构类似于以下
├── cli
│ └── cli.php
├── composer.json
├── composer.lock
├── config
│ └── main.php
├── controllers
│ ├── Cli.php
│ ├── System.php
│ └── Test.php
├── extensions
│ └── core
│ ├── AbstractAuth.php
│ ├── AbstractModel.php
│ ├── Auth.php
│ ├── Process.php
│ └── SystemCtrlInterface.php
├── LICENSE
├── models
│ └── Test.php
├── README.md
├── vendor
├── views
│ ├── layouts
│ │ └── main.php
│ ├── snippets
│ │ └── topbar.php
│ └── views
│ ├── system
│ │ ├── page401.php
│ │ ├── page403.php
│ │ └── page404.php
│ └── test
│ └── default.php
└── web
└── index.php
主要配置文件
任何应用程序(控制台或 Web)的配置都包含在文件 /config/main.php 中。这是一个关联数组,当微型框架核心加载时会被读取。数组可供修改和扩展,默认包括以下元素
return [ // The path map of the application components 'componentsRootMap' => [ 'models' => '/models', 'controllers' => '/controllers', 'views' => '/views/views', 'layouts' => '/views/layouts', 'snippets' => '/views/snippets', 'images' => '/web/assets/imgs', 'js' => '/web/js', 'css' => '/web/css' ], // The default name of the layout 'defaultLayout' => 'main', // The routing table that contains the correspondence between the request URL // and the Controller-Action pair 'routeMap' => [ 'cli' => [ '--test' => [ 'ctrl' => ['Cli', 'actionTest'], ], ], 'get' => [ '/' => [ 'ctrl' => ['Test', 'actionDefault'], 'auth' => true, ], '.*' => [ 'ctrl' => ['System', 'actionPage404'], 'auth' => false, ], ], 'post' => [ '.*' => [ 'ctrl' => ['System', 'actionPage404'], 'auth' => false, ], ], 'put' => [ '.*' => [ 'ctrl' => ['System', 'actionPage404'], 'auth' => false, ], ], 'delete' => [ '.*' => [ 'ctrl' => ['System', 'actionPage404'], 'auth' => false, ], ], ], // Database settings 'db' => is_readable(__DIR__.'/db.local.php') ? require __DIR__.'/db.local.php' : [ 'host' => '', 'username' => '', 'password' => '', 'dbname' => '', 'charset' => '', ], ];
通过调用
\nadir2\core\AppHelper::getInstance()->getConfig('configName')
控制器
控制器是继承自 \nadir2\core\AbstractWebController 或 \nadir2\core\AbstractCliController 抽象超类的一个实例。当被调用时,控制器执行一些操作,通常是指向模型以获取数据,对数据进行进一步转换并传递给视图。
控制器和视图
在 Web 应用程序的生命周期中,在查询绑定到控制器-动作对之后,会创建一个控制器对象,该对象默认尝试将视图对象与之关联。
视图通常是复合的,由布局(\nadir2\core\Layout 类的实例)和狭义的视图(\nadir2\core\View 类的对象)组成。默认的视图对象仅在存在相关标记文件时分配。标记文件的名称是通过从动作名称中删除 'action' 前缀获得的,文件位于以控制器名称命名的目录中(文件名和目录名应小写)。
通过调用访问器 $this->getView()、$this->setView()、$this->getLayout() 和 $this->setLayout(),在控制器中可以获得视图对象。在页面渲染开始之前,可以更改默认布局或视图为任何其他可用的单类型对象。
从控制器传递用户变量的值到视图
namespace controllers; use nadir2\core\AbstractWebCtrl; class Test extends AbstractWebCtrl { public function actionDefault(): void { // ... $this->getView()->foo = 'foo'; $this->getView()->bar = [42, 'bar']; $this->getView()->setVariables([ 'baz' => 'baz', 'qux' => 'qux', 'quux' => 'quux', ]); // ... $this->render(); } }
在这个视图的标记文件 /views/views/test/default.php 中,可以通过调用 $this->foo、$this->bar、$this->baz 等来读取变量。
通过在动作中调用 $this->render() 来渲染页面。您可以渲染只包含视图文件的页面(在这种情况下,布局必须是 null)。此外,在 AJAX 请求的情况下,通常不需要渲染 HTML 页面,需要更具体的答案格式,在这种情况下提供了 \nadir2\core\AbstractWebCtrl::renderJson() 方法。
CLI 控制器
shell 命令的示例
php cli.php --test --foo=bar
此命令在查询与路由表绑定后将被 CLI 控制器动作处理
namespace controllers; use nadir2\core\AbstractCliCtrl; class Cli extends AbstractCliCtrl { public function actionTest(array $args): void { if (!empty($args)) { $this->printInfo('The test cli action was called with args: ' .implode(', ', $args).'.'); } else { $this->printError(new \Exception('The test cli action was called without args.')); } } }
视图
复合视图
视图包含HTML代码和最小逻辑,这些逻辑仅用于处理从控制器接收的操作变量。视图通常是复合的,由布局(\nadir2\core\Layout类的实例)和狭义上的视图(\nadir2\core\View类的对象)组成。
视图的每个复合部分都可以进一步包含代码片段(\nadir2\core\Snippet类的对象)——这些是界面中经常遇到元素的片段,例如导航面板、各种信息块等。
namespace controllers; use nadir2\core\AbstractWebCtrl; class Test extends AbstractWebCtrl { public function actionDefault(): void { // ... $this->setView('test', 'default'); $this->setLayout('main'); $this->getLayout()->isUserOnline = false; $this->getView()->foo = 'foo'; $this->getView()->bar = [42, 'bar']; // ... $this->render(); } }
在此视图的标记文件/views/views/test/default.php中,可以通过调用$this->foo和$this->bar来读取视图变量。
<!-- ... -->
<div>
<h1><?= $this->foo; ?></h1>
<?php if (is_array($this->bar) && !empty($this->bar)): ?>
<ul>
<?php foreach ($this->bar as $elem): ?>
<li><?= $elem; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<!-- ... -->
同样,在布局的标记文件/views/layouts/main.php中,可以通过调用$this->isUserOnline来读取变量。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Nadir2 Microframework</title>
</head>
<body>
<?php $this->getView()->render(); ?>
</body>
</html>
请注意,视图在布局中的渲染位置由方法调用$this->getView()->render()的位置决定。
代码片段
与复合体(视图和布局)相比,代码片段的工作方式相似。代码片段的类也继承了\nadir2\core\AbstractView类,用户变量的发送和调用过程与布局和视图类似。复合体可以包含多个代码片段,但代码片段不能包含另一个代码片段。
我们将从上一个示例的标记文件中提取一部分内容到独立的代码片段topbar。文件/views/snippets/topbar.php将包含以下代码
<h1>User <?= $this->isUserOnline ? 'online' : 'offline'; ?></h1>
控制器动作将如下所示
namespace controllers; use nadir2\core\AbstractWebCtrl; class Test extends AbstractWebCtrl { public function actionDefault(): void { // ... $this->setView('test', 'default'); $this->setLayout('main'); $this->getView()->addSnippet('topbar'); $this->getView() ->getSnippet('topbar') ->isUserOnline = false; $this->getView()->foo = 'foo'; $this->getView()->bar = [42, 'bar']; // ... $this->render(); } }
代码片段topbar在视图中的渲染位置由方法调用$this->getSnippet('topbar')->render()的位置决定。
<!-- ... -->
<div>
<?php $this->getSnippet('topbar')->render(); ?>
<h1><?= $this->foo; ?></h1>
<?php if (is_array($this->bar) && !empty($this->bar)): ?>
<ul>
<?php foreach ($this->bar as $elem): ?>
<li><?= $elem; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<!-- ... -->
模型
Nadir2不包含构建模型的特定规则和规范。MVC模式中这一组件的具体实现由开发者负责。根据许多因素,模型可以表示为一层(单个对象),也可以表示为多个抽象层(复杂的相关对象层次结构)。
namespace models; use extensions\core\AbstractModel; class Test extends AbstractModel { public function readDefault(): array { // Dummy mode return [ 'foo' => 'bar', 'bar' => [42, 'baz'], ]; } }
namespace controllers; use nadir2\core\AbstractWebCtrl; class Test extends AbstractWebCtrl { public function actionDefault(): void { $this->getView()->addSnippet('topbar'); $this->getView() ->getSnippet('topbar') ->isUserOnline = false; $data = (new \models\Test())->readDefault(); $this->getView()->foo = $data['foo']; $this->getView()->bar = $data['bar']; $this->render(); } }
授权
Nadir2提供了广泛的自定义选项用于用户授权。需要填充\extensions\core\Auth类以实现具体功能。
namespace extensions\core; use nadir2\core\Request; use nadir2\core\AppHelper; /** * This is the auth general class for custom extension. */ class Auth extends AbstractAuth { /** @var \nadir2\core\Request The request object. */ protected $request = null; /** @var array The route config. */ protected $routeConfig = null; /** @var mixed The occured error instance. */ protected $error = null; /** * The constructor receives the request object to read the route configuration. * @param \nadir2\core\Request $request The request object. */ public function __construct(Request $request) { $this->request = $request; $this->routeConfig = AppHelper::getInstance()->getRouteConfig(); } /** * It checks cookies according custom algorithm. * @param array $cookies The associated array of cookies. * @return void */ protected function checkCookies(array $cookies): void { // Put your code here... } /** * The main executable method of this class. * @throws \Exception It's throwen if 'auth' option wasn't defined. */ public function run(): void { if (!isset($this->routeConfig['auth'])) { throw new \Exception("Undefined option 'auth' for the current route."); } $cookies = $this->request->getAllCookies(); $this->checkCookies(!is_null($cookies) ? $cookies : []); } /** * The method checks if the user auth is valid. * @return boolean */ public function isValid(): bool { return is_null($this->error); } /** * The method contains the code which is invoked if the auth was failed. */ public function onFail() { // Put your code here... } }
要实现基于角色的访问控制,还应在主配置文件中的路由中添加额外的选项。
'routeMap' => [ // ... 'get' => [ '/' => [ 'ctrl' => ['Test', 'actionDefault'], 'roles' => ['admin', 'manager'], 'auth' => true, ], // ... '.*' => [ 'ctrl' => ['System', 'actionPage404'], 'roles' => ['admin', 'manager', 'user'], 'auth' => true, ], ), // ... ],
数据验证
nadir2\core\validator\Validator类提供了输入数据的验证。其功能可以通过添加新的自定义验证规则进行扩展。
namespace controllers; use nadir2\core\AbstractWebCtrl; use nadir2\core\validator\Validator; class Test extends AbstractWebCtrl { public function actionDefault(): void { $data = [ 'foo' => 'fooValue', 'bar' => 'barValue', 'baz' => -42, 'qux' => false, 'quux' => [ 'quuux' => 'quuuxValue', ], ]; $validator = new Validator($data); $validator->setItems([ [ ['foo', 'bar'], 'required', ], [ [ 'foo', 'bar', 'quux.quuux', ], 'string', ['notEmpty' => true], ], [ 'bar', 'string', [ 'length' => ['min' => 3, 'max' => 8], 'pattern' => '#^bar#', ] ], [ 'baz', 'number', [ 'integer' => true, 'float' => false, 'positive' => false, 'value' => ['max' => -1], ] ], [ 'qux', 'boolean', ['isTrue' => false], ], [ 'quux', 'array', [ 'assoc' => true, 'length' => ['equal' => 1], ], ], ])->run(); if ($validator->isValid()) { $this->renderJson([ 'result' => 'ok', 'errors' => [], ]); } else { $this->renderJson([ 'result' => 'fail', 'errors' => $validator->getErrors(), ]); } } }