tobento / app-view
应用视图支持。
Requires
- php: >=8.0
- tobento/app: ^1.0
- tobento/app-migration: ^1.0
- tobento/css-basis: ^1.0
- tobento/service-dater: ^1.0.2
- tobento/service-form: ^1.0
- tobento/service-language: ^1.0
- tobento/service-menu: ^1.1.0
- tobento/service-table: ^1.0.2
- tobento/service-translation: ^1.0
- tobento/service-uri: ^1.0
- tobento/service-view: ^1.0
Requires (Dev)
- phpunit/phpunit: ^9.5
- tobento/app-http: ^1.0.3
- tobento/service-filesystem: ^1.0
- vimeo/psalm: ^4.0
Suggests
- tobento/app-http: App Http for supporting forms and routing
- tobento/app-i18n: App I18n for supporting translations
README
应用视图支持创建菜单、表单等,以创建任何类型的Web应用程序。它包含一个默认布局,使用Basis Css,你可以使用或不使用。虽然某些应用包可能依赖于它。
目录
入门指南
运行此命令以添加运行中的应用视图项目的最新版本。
composer require tobento/app-view
要求
- PHP 8.0 或更高版本
文档
应用
如果你使用的是骨架,请查看 应用骨架。
你也可以查看 应用 以了解有关应用程序的一般信息。
视图启动
视图启动执行以下操作:
- 迁移默认布局的视图和CSS资产
- 实现ViewInterface
- 如果可用,则添加来自不同服务的全局视图数据
- 添加多个视图宏
use Tobento\App\AppFactory; // Create the app $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'vendor', 'vendor') ->dir($app->dir('app').'views', 'views', group: 'views') ->dir($app->dir('root').'public', 'public'); // Adding boots $app->boot(\Tobento\App\View\Boot\View::class); // Run the app $app->run();
渲染视图
你可以以几种方式渲染视图
使用应用程序
use Tobento\App\AppFactory; use Tobento\Service\View\ViewInterface; // Create the app $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'vendor', 'vendor') ->dir($app->dir('app').'views', 'views', group: 'views') ->dir($app->dir('root').'public', 'public'); // Adding boots $app->boot(\Tobento\App\View\Boot\View::class); $app->booting(); $view = $app->get(ViewInterface::class); $content = $view->render(view: 'about', data: []); // or using the app macro: $content = $app->renderView(view: 'about', data: []); // Run the app $app->run();
使用自动注入
你还可以在任何由应用程序解析的类中请求 ViewInterface::class
。
use Tobento\Service\View\ViewInterface; class SomeService { public function __construct( protected ViewInterface $view, ) {} }
使用视图启动
use Tobento\App\Boot; use Tobento\App\View\Boot\View; class AnyServiceBoot extends Boot { public const BOOT = [ // you may ensure the view boot. View::class, ]; public function boot(View $view) { $content = $view->render(view: 'about', data: []); } }
查看 视图服务 以了解更多信息。
使用响应器
如果你已启动 App Http - 请求者和响应者 启动,则可以使用 render
方法
use Tobento\Service\Responser\ResponserInterface; use Psr\Http\Message\ResponseInterface; class SomeHttpController { public function index(ResponserInterface $responser): ResponseInterface { return $responser->render(view: 'register', data: []); } }
全局视图数据和变量
它添加了以下可在视图文件中访问的全局视图数据和变量
// by variable: $htmlLang $locale $routeName // by view get method: $htmlLang = $view->get('htmlLang', 'en'); $locale = $view->get('locale', 'en'); $routeName = $view->get('routeName', '');
$htmlLang / $locale
视图启动的代码片段
use Tobento\Service\Language\LanguagesInterface; if ($this->app->has(LanguagesInterface::class)) { $locale = $this->app->get(LanguagesInterface::class)->current()->locale(); $view->with('htmlLang', str_replace('_', '-', $locale)); $view->with('locale', $locale); }
$routeName
视图启动的代码片段
use Tobento\Service\Routing\RouterInterface; if ($this->app->has(RouterInterface::class)) { $matchedRoute = $this->app->get(RouterInterface::class)->getMatchedRoute(); $view->with('routeName', $matchedRoute?->getName() ?: ''); }
添加全局数据和变量
你可以通过使用启动添加更多全局数据
use Tobento\App\Boot; use Tobento\Service\View\ViewInterface; class SomeGlobalViewDataBoot extends Boot { public function boot() { // only add if view is requested: $this->app->on(ViewInterface::class, function(ViewInterface $view) { // using the with method: $view->with(name: 'someVariable', value: 'someValue'); // using the data method: $view->data([ 'someVariable' => 'someValue', ]); }); } }
可用的视图宏
app
返回应用程序实例。
use Tobento\App\AppInterface; var_dump($view->app() instanceof AppInterface); // bool(true)
assetPath
assetPath
函数返回应用程序资产目录的完全限定路径。
var_dump($view->assetPath('assets/editor/script.js')); // string(33) "/basepath/assets/editor/script.js"
menu
返回指定名称的菜单。
use Tobento\Service\Menu\MenuInterface; var_dump($view->menu('main') instanceof MenuInterface); // bool(true)
查看 菜单服务 以了解更多信息。
trans / etrans
如果应用程序中可用,则返回翻译器翻译的消息。
默认情况下,翻译器不可用,你可能需要安装 App Translation 包来这样做。
$translated = $view->trans( message: 'Hi :name', parameters: [':name' => 'John'], locale: 'de', ); // The etrans method will escape the translated message // with htmlspecialchars. echo $view->etrans( message: 'Hi :name', parameters: [':name' => 'John'], locale: 'de', );
routeUrl
如果应用程序中可用 Tobento\Service\Routing\RouterInterface
,则返回指定路由的URL,这是在启动了 App Http - 路由启动 时的情况。
use Tobento\Service\Routing\UrlInterface; $url = $view->routeUrl( name: 'route.name', parameters: [], ); var_dump($url instanceof UrlInterface); // bool(true)
tagAttributes
返回指定标签名称的属性。
use Tobento\Service\Tag\AttributesInterface; var_dump($view->tagAttributes('body') instanceof AttributesInterface); // bool(true)
查看标签服务 - 属性接口以了解更多相关信息。
日期 / dateTime / formatDate
您可以使用date
、dateTime
和formatDate
方法,通过日期格式化器来格式化和显示任何日期。
// date: var_dump($view->date('2024-02-15 10:15')); // string(27) "Thursday, 15. February 2024" var_dump($view->date( value: 'now', format: 'EE, dd. MMMM yyyy', locale: 'de_DE', )); // string(19) "Sa., 23. März 2024" // dateTime: var_dump($view->dateTime('now')); // string(31) "Saturday, 23. March 2024, 07:36" var_dump($view->dateTime( value: 'now', format: 'EE, dd. MMMM yyyy, HH:mm', locale: 'de_DE', )); // string(26) "Sa., 23. März 2024, 07:45" // formatDate: var_dump($view->formatDate( value: '2024-02-15 10:15', format: 'd.m.Y H:i', )); // string(16) "15.02.2024 10:15"
菜单启动
菜单引导程序执行以下操作
- 实现菜单接口
- 添加菜单视图宏
use Tobento\App\AppFactory; use Tobento\Service\Menu\MenusInterface; use Tobento\Service\Menu\MenuInterface; use Tobento\Service\View\ViewInterface; // Create the app $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'vendor', 'vendor') ->dir($app->dir('app').'views', 'views', group: 'views') ->dir($app->dir('root').'public', 'public'); // Adding boots $app->boot(\Tobento\App\View\Boot\View::class); // no need to boot as already loaded by the view boot: // $app->boot(\Tobento\App\View\Boot\Menus::class); $app->booting(); // Get the menus: $menus = $app->get(MenusInterface::class); // View menu macro: $view = $app->get(ViewInterface::class); var_dump($view->menu('main') instanceof MenuInterface); // bool(true) // Run the app $app->run();
查看 菜单服务 以了解更多信息。
使用菜单引导程序
您可以使用菜单引导程序添加菜单项
use Tobento\App\Boot; use Tobento\App\View\Boot\Menus; class AnyServiceBoot extends Boot { public const BOOT = [ // you may ensure the menus boot. Menus::class, ]; public function boot(Menus $menus) { $menu = $menus->menu('main'); $menu->link('https://example.com/foo', 'Foo')->id('foo'); } }
使用app方法
只有当从应用程序请求菜单时,您才能添加菜单项
use Tobento\App\Boot; use Tobento\Service\Menu\MenusInterface; class AnyServiceBoot extends Boot { public function boot() { $this->app->on(MenusInterface::class, function(MenusInterface $menus) { $menu = $menus->menu('main'); $menu->link('https://example.com/foo', 'Foo')->id('foo'); }); } }
表单启动
表单引导程序执行以下操作
- 实现表单工厂接口
- 添加表单视图宏
- 添加VerifyCsrfToken中间件
此引导程序需要app-http包
composer require tobento/app-http
use Tobento\App\AppFactory; use Tobento\Service\Form\FormFactoryInterface; use Tobento\Service\Form\Form; use Tobento\Service\View\ViewInterface; // Create the app $app = (new AppFactory())->createApp(); // Add directories: $app->dirs() ->dir(realpath(__DIR__.'/../'), 'root') ->dir(realpath(__DIR__.'/app/'), 'app') ->dir($app->dir('app').'config', 'config', group: 'config') ->dir($app->dir('root').'vendor', 'vendor') ->dir($app->dir('app').'views', 'views', group: 'views') ->dir($app->dir('root').'public', 'public'); // Adding boots $app->boot(\Tobento\App\View\Boot\View::class); $app->boot(\Tobento\App\View\Boot\Form::class); $app->booting(); // Get the form factory: $formFactory = $app->get(FormFactoryInterface::class); // View form macro: $view = $app->get(ViewInterface::class); $form = $view->form(); // var_dump($form instanceof Form); // bool(true) // Run the app $app->run();
查看表单服务以了解更多相关信息。
您可能还需要引导App Http - 错误处理器引导程序,它已经处理了由表单引起的异常。
CSRF保护
CSRF保护已实现,并且CSRF令牌将由已注册的VerifyCsrfToken中间件进行验证。
CSRF令牌
表单引导程序将CSRF令牌添加到全局视图数据,该数据可在您的视图文件中访问
$token = $view->data()->get('csrfToken');
X-CSRF-TOKEN
除了检查作为POST参数的CSRF令牌之外,VerifyCsrfToken中间件还将检查X-CSRF-TOKEN
请求头。
当使用默认布局时,在视图文件inc/head
中,令牌将存储在名为csrf-token
的HTML元标签中
<meta name="csrf-token" content="token-string">
您可以使用此元标签在设置X-CSRF-TOKEN
头时获取令牌
fetch("https://example.com", { method: "POST", headers: { "X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').getAttribute('content') }, body: JSON.stringify(data) });
表单消息
由于Tobento\Service\Form\ResponserFormFactory::class
是默认的Tobento\Service\Form\FormFactoryInterface::class
实现,您可以通过以下方式将消息传递给表单字段
use Tobento\Service\Responser\ResponserInterface; use Tobento\Service\Requester\RequesterInterface; use Psr\Http\Message\ResponseInterface; class RegisterController { public function index(ResponserInterface $responser): ResponseInterface { // set the key corresponding to your form field name: $responser->messages()->add('info', 'Some info message', key: 'firstname'); return $responser->render(view: 'register'); } public function register( RequesterInterface $requester, ResponserInterface $responser, ): ResponseInterface { // validate request data: //$requester->input(); // add message on error: $responser->messages()->add('error', 'Message error', key: 'firstname'); // redirect - messages and input data will be flashed: return $responser->redirect( uri: 'uri', )->withInput($requester->input()->all()); } }
在您的视图文件中
<?php $form = $view->form(); ?> <?= $form->form() ?> <?= $form->input( type: 'text', name: 'firstname', ) ?> <?= $form->button('Register') ?> <?= $form->close() ?>
消息启动
// ... $app->boot(\Tobento\App\View\Boot\Messages::class); // ...
消息引导程序执行以下操作
如果传入的视图消息是Tobento\Service\Message\MessagesInterface
的实例,则渲染视图消息
use Tobento\Service\Message\MessagesInterface; use Tobento\Service\Message\Messages; class SomeHttpController { public function index(ViewInterface $view): string { $messages = new Messages(); $messages->add(level: 'error', message: 'Error message'); return $view->render( view: 'register', data: [ 'messages' => $messages, ], ); } }
查看消息服务以了解更多相关信息。
响应器消息
如果已引导App Http - 请求者与响应者引导程序,则渲染响应器中的消息
use Tobento\Service\Responser\ResponserInterface; use Psr\Http\Message\ResponseInterface; class SomeHttpController { public function index(ResponserInterface $responser): ResponseInterface { $responser->messages()->add('info', 'Message info'); $responser->messages()->add('success', 'Message success'); $responser->messages()->add('error', 'Message error'); $responser->messages()->add('warning', 'Message warning'); // if a key is specified, the message will not be rendered, // as it may belong to a form field. $responser->messages()->add('error', 'Message error', key: 'firstname'); return $responser->render(view: 'register'); } }
视图文件
在您的视图文件中,渲染消息视图
<?= $view->render('inc.messages') ?>
面包屑启动
// ... $app->boot(\Tobento\App\View\Boot\Breadcrumb::class); // ...
面包屑引导程序执行以下操作
添加面包屑视图并创建基于主菜单的面包屑菜单,使用全局视图数据中的routeName
确定活动菜单树。
视图文件
在您的视图文件中,渲染面包屑视图
<?= $view->render('inc.breadcrumb') ?>
表格启动
表格引导程序执行以下操作
- 将table.css添加到您指定的公共CSS目录
public/assets/css/table.css
- 添加表格视图宏
// ... $app->boot(\Tobento\App\View\Boot\Table::class); // ...
视图文件
<?php $table = $view->table(name: 'demo'); $table->row([ 'title' => 'Title', 'description' => 'Description', ])->heading(); $table->row([ 'title' => 'Lorem', 'description' => 'Lorem ipsum', ]); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Demo Table</title> <?= $view->assets()->render() ?> <?php // add table.css $view->asset('assets/css/table.css'); ?> </head> <body> <?= $table ?> </body> </html>
查看表格服务以了解更多相关信息。
默认布局
视图
默认布局使用 public/assets/css/app.css
和 Basis Css public/assets/css/basis.css
来样式化视图文件。
使用视图启动器的一个视图文件可能看起来像
<!DOCTYPE html> <html lang="<?= $view->esc($view->get('htmlLang', 'en')) ?>"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Demo View</title> <?= $view->render('inc/head') ?> <?= $view->assets()->render() ?> </head> <body<?= $view->tagAttributes('body')->add('class', 'page')->render() ?>> <?= $view->render('inc/header') ?> <?= $view->render('inc/nav') ?> <main class="page-main"> <?= $view->render('inc.breadcrumb') ?> <?= $view->render('inc.messages') ?> <h1 class="text-xl">Demo View</h1> </main> <?= $view->render('inc/footer') ?> </body> </html>
不建议修改现有的视图和资源,因为任何应用程序更新都可能覆盖这些文件。相反,创建一个 主题。
异常视图
您的视图目录中提供以下异常视图
exception/error.php
exception/error.xml.php
您可以安装并启动 App Http - 错误处理器启动,该启动器将使用这些视图来渲染 html 和 xml 异常。
使用响应器渲染异常视图的示例
如果你已启动 App Http - 请求者和响应者 启动,则可以使用 render
方法
use Tobento\Service\Responser\ResponserInterface; use Psr\Http\Message\ResponseInterface; class SomeHttpController { public function index(ResponserInterface $responser): ResponseInterface { return $responser->render( view: 'exception/error', data: [ 'code' => '403', 'message' => 'Forbidden', ], code: 403, ); } }
主题
主题视图
您可以创建一个 "主题" 来自定义现有的视图和资源,否则任何应用程序更新都可能覆盖这些文件。
首先,创建一个主题启动器
use Tobento\App\Boot; class SomeThemeBoot extends Boot { public function boot() { // add a new view directory to load views from // with a higher priority as the default. $this->app->dirs()->dir( dir: $this->app->dir('app').'/theme/', name: 'theme', group: 'views', priority: 500, // default is 100 ); } }
接下来,只需将您想要自定义的视图文件放置在指定的目录中。如果视图文件不存在,它将使用默认视图文件。
主题资产
您可以通过以下方式处理您自定义的资源
替换资源
在您自定义的视图中,您可以直接用您自定义的资源替换资源
$view->asset('assets/css/my-app.css');
使用资源处理器
您可以创建一个资源处理器来压缩、合并或替换资源。
use Tobento\App\Boot; use Tobento\Service\View\ViewInterface; use Tobento\Service\View\AssetsHandlerInterface; class SomeThemeBoot extends Boot { public function boot() { $this->app->on(ViewInterface::class, function(ViewInterface $view) { $view->assets()->setAssetsHandler( assetsHandler: $assetsHandler, // AssetsHandlerInterface ); }); } }
一个默认的资源处理器正在开发中,用于压缩、合并或替换资源!