delatbabel / viewpages
支持从数据库中渲染/查看 Laravel 页面和模板。
Requires
- php: >=5.4.0
- aws/aws-sdk-php: ^3.33
- delatbabel/applog: dev-master
- delatbabel/fluents: ~1.0
- delatbabel/nestedcategories: dev-master
- delatbabel/site-config: dev-master
- illuminate/support: ^5.1
- illuminate/view: ^5.1
- league/flysystem-aws-s3-v3: ^1.0
- rcrowe/twigbridge: ^0.9.2
- twig/twig: 1.*
This package is not auto-updated.
Last update: 2024-09-14 18:52:57 UTC
README
支持从数据库中查看/渲染 Laravel 页面和模板。
可用于内容管理、管理界面(例如使用 AdminLTE 或其他前端框架)等。
支持使用与现有视图加载相同的接口加载 Blade 和 Twig 模板,例如
return View::make("dashboard.sysadmin") ->with('page_title', 'System Administrator Dashboard') ->with('tasks', $tasks);
原理
无法使用数据库支持的视图、模板和布局是阻止 Laravel 被用来创建真正动态 CMS 的缺失功能之一。本软件包旨在解决这个问题。
TerrePorter 通过他的 StringBladeCompiler 软件包部分解决了这个问题。他的软件包最初基于 Flynsarmy/laravel-db-blade-compiler,该软件包支持从模型对象中获取 blade,但现已不再维护。
安装
使用命令行通过 composer 添加软件包
composer require delatbabel/viewpages
或者,通过将这些行添加到您的 composer.json 文件中手动拉取软件包
"require": {
"delatbabel/viewpages": "~1.0"
},
完成后,运行 composer update 命令
composer update
注册服务提供者
在 composer update 完成后,从您的 config/app.php 文件中的 'providers' 数组中删除此行(或将其注释掉)
Illuminate\View\ViewServiceProvider::class
替换为以下行
Delatbabel\ViewPages\ViewPagesServiceProvider::class,
整合并运行迁移
最后,整合并运行以下迁移脚本来创建数据库表
php artisan vendor:publish --provider='Delatbabel\ViewPages\ViewPagesServiceProvider' --force
php artisan migrate
在运行迁移脚本之前,您可能想修改脚本本身,或者修改数据库/seeds/examples 中包含的基本模板。提供的只是一些基于 AdminLTE 的示例。
如何使用此软件包
创建视图
- 按上述说明安装和运行迁移。
- 将模板填充到 vpages 表中。它们不必与标准 Laravel blade 模板有任何不同 - 请参阅以下关于 Blade 编译 的部分。
- 除了包含模板或页面内容的 content 列之外,还应填充以下列
- pagekey -- 页面查找键。
- url -- 页面查找 URL,当您想通过 URL 查找页面内容时可能很有用。
- name -- 页面的描述性名称,例如 "主网站主页"。
- description -- 页面的更长时间描述。
- pagetype -- 根据视图语言为 blade.php 或 twig。
这里重要的是 pagekey。它基本上取代了现有 Laravel View 门面中用于查找视图的视图名称。例如,如果您通常使用 View::make("dashboard.sysadmin");
来查找视图,您通常会将视图存储在磁盘上的 resources/views/dashboard/sysadmin.blade.php
中。相反,您会将视图存储在 vpages 表中,如下所示
pagekey
="dashboard.sysadmin"
url
="dashboard/sysadmin"
name
= 您喜欢作为名称的任何内容,例如 "sysadmin dashboard"description
= 您喜欢的任何内容,例如 "我的系统管理员仪表板"content
= 您通常存储在磁盘上的确切 blade 内容pagetype
=blade.php
。
创建 Blade 模板
您仍然可以像在 Laravel 中一样使用模板(布局)。例如,您的模板可以包含以下内容
<html> <head><title>{{ $page_title }}</title></head> <body> @yield('body) </body> </html>
然后,主体可以包含以下内容
@extends('layouts.main') @section('body) <p>Body text goes here</p> @endsection
将模板存储在vpages表中,其中pagekey = 'layouts.main',它将被您的主体视图自动找到并扩展。
有关更多详细信息,请参阅模板继承。
变体内容
这些是网站依赖的数据块,存储在vobjects表中,并使用可以注入页面的VojbecService服务检索,该服务使用Laravel 服务注入。
示例
@inject('objects', 'Delatbabel\ViewPages\Services\VobjectService') <!-- Using the regular make method --> <title> {{ $objects->make('page_title') }} </title> <!-- Using a magic getter --> <title> {{ $objects->page_title }} </title>
Twig 视图和模板
此包现在支持使用Twig模板语言以及blade模板,通过TwigBridge类。
使用pagetype
= "twig"将这些存储在数据库表中,与您的blade模板一起。
使用模板
一旦创建模板,您就可以像使用任何其他视图文件一样使用它们,例如。
return view("dashboard.sysadmin") ->with('page_title', 'System Administrator Dashboard') ->with('tasks', $tasks);
请注意,Laravel 视图类中有一个错误,它在其所有情况下都使用其本地工厂,而不是从应用程序实例中获取工厂,因此请使用view()辅助函数而不是外观类View::make()。
基础Factory类将按以下顺序尝试查找视图,直到找到匹配项
- 在vpages表中查找pagekey = dashboard.sysadmin的vpage。
- 在vpages表中查找url = dashboard.sysadmin的vpage。
- 在磁盘上查找名为resources/views/sysadmin/dashboard.blade.php的视图。
- 在磁盘上查找名为resources/views/sysadmin/dashboard.twig的视图。
- 在vpages表中查找pagekey = errors.410的vpage。
- 在磁盘上查找名为resources/views/errors/410.blade.php的视图。
- 在磁盘上查找名为resources/views/errors/410.twig的视图。
- 在vpages表中查找pagekey = errors.404的vpage。
- 在磁盘上查找名为resources/views/errors/404.blade.php的视图。
- 在磁盘上查找名为resources/views/errors/404.twig的视图。
有关如何工作的详细信息,请参阅后面的“架构”部分。
CMS 使用
包含一个名为VpageController的控制器类(您欢迎扩展它),可以用作通配路由。该控制器包含一个名为index()
的函数,该函数简单地使用提供的URL从数据库中加载页面。这为您提供了一个简单的基于blade的CMS。
要包含到这个控制器的路由,请在您的路由文件(s)中所有其他路由之后包含此路由规范。
Route::any('{slug}', [ 'as' => 'vpage.make', 'uses' => '\Delatbabel\ViewPages\Http\Controllers\VpageController@make' ])->where('slug', '.*');
您可能需要在此路由上添加额外的where子句,以排除应用程序消耗的其他路由。例如,要排除所有位于"/admin"和"/img"下的URL,请添加以下where子句
->where('slug', '^(?!admin)(?!img)([A-z\d-\/_.]+)?');
待办事项
- 从make函数中提取查找特定网站页面的逻辑,并将其放入自定义的BelongsToMany类中。
- 更多测试。这个似乎在我的已导入示例应用程序中工作,但我还没有进行过广泛的测试或构建phpunit测试用例。
- 更多文档。
- 也许有一组管理控制器来更新/编辑数据库中的内容。使用浏览器内编辑器,例如HTMLiveCode。
- 修复TwigEngine的finder,使其不查找Blade视图,反之亦然。在
Vpage::make()
中也有相关的待办事项,以限制从数据库中检索的视图类型。 - 向加载器添加lastModified()函数。
- 修复BladeCompiler中的isExpired()函数。
- 也许支持其他模板引擎,如Smarty。特别是,我更喜欢一个不编译为PHP代码而编译为内存中字符串的模板引擎。
呼叫
此包派生的原始包有呼叫的想法。这意味着视图可以包含如下的调用
{{ __o('toolbox@functionname') }}
__o 是一个辅助函数,用于在 Laravel 3 中调用 Controller::call() 函数,以 HMVC 的方式渲染 toolbox 控制器中 "functionname" 动作的输出。实际上,Laravel 5 已经不再支持 HMVC(Taylor 认为HMVC 是一个糟糕的主意,我并不这么认为),因此我们需要找到其他方式来引入动态内容。这可能会通过某种方式通过 Repository 或 Service 类来实现。
服务注入可能已经可以工作,但我还没有测试过。
架构
我使用过一个基于 Laravel 3 的 CMS 系统,其实现相当糟糕,这个包旨在成为 Laravel 3 CMS 应该实现的最佳实践。
工厂
视图系统的根是 Factory
类。这通过 View
门面访问,并在应用中注册了一个 view
单例。
我对 Laravel 默认的 Factory
类所做的唯一修改是自动加载 errors.410 视图或 errors.404 视图,如果请求的视图未找到。这在 CMS 应用程序中很有用,其中视图可能通过 URL 路径进行搜索,用户可能会输入一个垃圾 URL,因此我们希望显示一个 404 页面而不是一个未处理的异常块。
扩展和引擎解析
Factory
类包含一个内部数组 $extensions
,它是一个将文件扩展名(特别是视图路径扩展名)映射到引擎 ID 的映射。默认数组看起来像这样
protected $extensions = ['blade.php' => 'blade', 'php' => 'php'];
所以 "blade.php" 扩展名映射到 "blade" 引擎 ID。
可以通过调用 Factory::addExtension()
向此 $extensions
数组添加额外的引擎扩展。注意,这已经被 TwigBridge 服务提供者完成。
引擎 ID 传递给一个解析器(EngineResolver
),它包含从引擎 ID 到引擎本身的映射。这些映射在 EngineResolver::resolve()
函数中创建。
因此,每次我们从数据库返回一个 blade 模板路径时,都需要在模板名称中添加 "blade.php" 扩展名,以便引擎解析器可以找到正确的引擎来编译和加载 blade。
引擎
Engine
类封装了加载、编译以及评估编译后的模板(包含数据)的功能。所有这些都在 get()
中发生,对于 blade 模板来说更为具体(或任何编译为 PHP 的模板),在 PhpEngine
类中有一个名为 evaluatePath()
的函数,它直接包含模板并通过将包含语句包裹在 ob_start()
和 ob_end_clean()
对对中(不高效)来获取输出(这是我讨厌将视图编译为 PHP 的原因之一)。
评估 Twig 模板需要不同于评估 Blade 模板,因为编译后的结果不能直接执行。注意,这已经在 TwigBridge
中的引擎中完成。
在编译期间加载 Blade 视图
Laravel 视图系统有些反直觉。每个编译器都调用 ViewFinders 来查找文件,然后在编译器中自己加载文件(而不是独立发生文件查找、加载和编译(从字符串))。
查找
这是通过以下方式实现的:
- 添加一个
VpageViewFinder
类,该类可以确定视图是否在数据库中找到。 - 添加了一个
ChainViewFinder
类,该类将VpageViewFinder和现有的Laravel FileViewFinder类(保持不变)链在一起,首先从数据库中选择视图,如果没有找到,则回退到文件系统。
EngineResolver
需要在视图名称中添加扩展名,以便能够确定正确的引擎将视图传递给它,因此VpageViewFinder
将文件扩展名添加到视图名称。可以剥去这个文件扩展名的Vpage
类可以从数据库加载页面,在make()
函数中加载页面之前。
请注意,扩展名不必以"."开头,可以有任何前缀。Vpage
类定义了一个要使用的常量前缀。
加载
通过以下方式将编译器扩展到包括加载阶段:
- 创建一个新的
LoaderInterface
,该接口定义了加载器的接口。加载器需要的唯一函数是get()函数,用于获取查找器找到的视图名称,并将其作为字符串加载。 - 创建新的
FilesystemLoader
和VpageLoader
类,可以从文件系统或数据库分别加载视图名称。 - 创建一个
ChainLoader
类,可以将VpageLoader
和FilesystemLoader
对象链接在一起,从任何位置加载视图。
最后一步是扩展原生的Laravel BladeCompiler
类,使用其中一个加载器(初始化为ChainLoader
)加载视图内容,而不是使用其自己的内部Filesystem
对象加载视图内容。然后使用compileString()
函数按正常方式编译。
加载Twig视图
查找
ChainViewFinder
类还可以由Twig
加载器在TwigBridge
服务提供商中创建,以便twig加载器的ViewFinder
可以使用与Blade加载器相同的方法查找视图。
加载
Twig
(通过TwigBridge
)已经有一个独立的查找器和加载器类的概念,但是加载器必须遵循Twig的Twig_LoaderInterface
。为了实现这一点,我构建了一个VpageTwigLoader
类来符合接口。
我覆盖了TwigBridge
ServiceProvider
类,以在已用于加载twig数据的Twig_Loader_Chain
对象中提供一个VpageTwigLoader
对象。
其他实现
在modules/backend/twig
中,OctoberCMS有一系列对twig类的扩展,用于加载等。他们有一个Twig_LoaderInterface
的实现,尽管它仍然本质上是一个基于文件的加载器,但它执行了一些额外的Laravely操作,例如在加载时触发事件,并使用自己的CMS类执行一些操作,例如使用Laravel的File
和Cache
门面加载文件(从磁盘或从缓存加载,如果存在)。这根本不是我想要做的。
处理视图名称
视图可以通过名称或URL找到。CMS可能更喜欢通过URL获取视图,而仅在工作视图名称的系统可能更喜欢通过页面键(例如layouts.master
)获取。工厂类首先尝试通过键查找,然后通过URL。
如果数据库中没有找到视图,则在磁盘上搜索具有该键的视图。
如果在磁盘上没有找到视图,则搜索数据库中的errors.410
和errors.404
视图。
如果在此阶段没有找到视图,则抛出异常。
Blade编译
Blade模板的编译有点像一门黑艺术,在Laravel文档中解释得也不够清楚。基本上,所有的blade模板都会编译成磁盘上的PHP文件,然后存储在storage/framework/views
目录下。一旦模板的编译版本过时,就会被新的副本替换。这些编译模板的缓存通常取决于blade模板文件的文件日期,然而在这个扩展中,我们让它取决于数据库中模板数据的updated_at
日期。
当一个blade编译为PHP时,指令的编译方式如下
@extends / @section
这两个指令是配对的。@extends编译成
echo $__env->make('layout.name', array_except(get_defined_vars(), array('__data', '__path')))->render();
@section和@endsection编译成
$__env->startSection('section_name'); $__env->stopSection();
注意,指令在编译文件中的顺序与在blade模板文件中的顺序相反——通常在模板文件中@extends在顶部,@section / @endsection在下面,在编译后的模板文件中@extends的编译版本在文件末尾。
@yield
@yield('section_name')在编译文件中看起来是这样的
echo $__env->yieldContent('body');
使用$__env
全局变量$__env
实际上是Illuminate\View\Factory
的一个实例。
这个类实现了必要的make()
、startSection()
、stopSection()
和yieldContent()
函数,这些函数使得内容出现在正确的位置。
关键函数是make()
,它执行以下操作
- 通过将'.'替换为'/'来标准化视图名称
- 调用查找器来查找视图。
- 准备数据数组。
- 从路径中获取要使用的引擎。
- 创建
View
对象
基础Factory
类的问题在于它假设视图名称是一个具有扩展名的文件名,并且可以通过“php”或“blade.php”或“twig”扩展名来识别以确定视图类型。相反,我将在数据库表中存储视图类型。
查找器和加载器已被扩展,以便它们能够从数据库而不是从磁盘中提取视图。从数据库中提取视图的逻辑全部在Vpage::make()
函数中。
一旦从数据库中提取了blade的内容并解决了引擎,则通过Factory::make()
将视图工厂和引擎传递给视图。
渲染
View::render()
通过以下方式执行实际渲染
- 调用
View::renderContents()
- 它调用
View::getContents()
- 它将完整视图路径和数据传递给
Engine::get()
Engine::get()
调用Compiler::compile()
来编译视图,如果缓存的编译视图副本已过期- 然后
Compiler::getCompiledPath()
,它返回编译视图的路径 - 然后
Engine::evaluatePath()
,它将数据传递给编译后的视图
服务提供者
这里的服务提供者相当简单——然而有3个
ViewPagesServiceProvider
——执行正常的迁移、种子注册,并调用StringBladeCompilerServiceProvider
。IlluminateViewServiceProvider
——扩展基础Laravel视图服务提供者,包括必要的附加查找器和加载器类。TwigBridgeServiceProvider
——扩展TwigBridge
中的服务提供者,以便twig链加载器包括一个从数据库中加载twig模板的类。
模型类
模型类(Vpage
)替换了模板文件的磁盘存储,这样上述讨论的Factory
类就可以从数据库而不是磁盘中提取模板。