midgard / midgardmvc-core
面向内容的 PHP 模型-视图-控制器库
Requires
- php: >=5.2.0
- phptal/phptal: >=1.2.2
- symfony/yaml: >=2.1.0
Suggests
- ext-midgard2: >=10.05.5
This package is not auto-updated.
Last update: 2024-09-14 12:51:53 UTC
README
Midgard MVC 是一个 PHP 网络框架。作为一个框架,它提供了一种标准的方式来构建和部署网络应用程序。您的应用程序提供一组接口,当发生匹配的 HTTP 请求时,Midgard MVC 会调用这些接口。
基本构建块
- 应用程序:一个配置文件,描述 Midgard MVC 网站中使用的组件和其他设置
- 组件:一个旨在作为网络应用程序一部分运行的 PHP 模块。例如:新闻列表。提供路由、控制器和模板
- 库:一个可以通过其他模块调用来执行某些任务的 PHP 模块。不作为网站的一部分运行。例如:表单验证库
- 服务:访问给定功能的标准接口。例如:身份验证
- 提供者:访问给定信息的标准接口。例如:层次结构。服务与提供者的区别在于,服务执行任务,而提供者仅仅提供访问特定类型数据的权限
安装
使用 Composer 安装
要使用 Composer 安装 Midgard MVC,只需将其添加到您的包依赖项中
"require": {
"midgard/midgardmvc-core": ">=10.05.0"
}
请注意,Midgard MVC 使用非命名空间代码,因此目前还不能与 Composer 生成的自动加载器一起使用。为了注册 Midgard MVC 自动加载器,请包含框架引导文件
require 'vendor/midgard/midgardmvc-core/framework.php';
使用 midgardmvc_installer
安装
安装 Midgard MVC 最简单的方法是使用 midgardmvc_installer
工具。您可以通过运行以下命令使用 PEAR 安装该工具
$ sudo pear channel-discover pear.indeyets.pp.ru
$ sudo pear install indeyets/midgardmvc_installer
之后,您可以通过将 Midgard MVC 安装器指向一个 应用程序配置 YAML 文件和一个目标目录来安装 Midgard MVC 应用程序。例如
$ midgardmvc install https://github.com/bergie/org_midgardproject_productsite/raw/master/rdf.yml ~/midgardexample
Midgard MVC 安装程序将安装 Midgard MVC、应用程序所需的所有组件和库。它还将为应用程序生成一个 Midgard2 数据库。现在运行应用程序变得容易
$ ~/midgardexample/run
默认情况下,用于运行 Midgard MVC 的 AppServer in PHP 将在 http://localhost:8001 中可用。
应用程序配置
应用程序配置是在 Midgard MVC 启动之前读取的配置文件,其中您可以定义应用程序范围的全局配置设置,包括覆盖 Midgard MVC 默认配置。在应用程序配置文件中定义了与应用程序一起使用的组件。
默认情况下,应用程序配置位于 Midgard MVC 安装目录的根目录中,名为 application.yml
的 YAML 文件中。示例
name: Example Blog
components:
midgardmvc_core
git: git://github.com/midgardproject/midgardmvc_core.git
midgardmvc_helper_forms
git: git://github.com/midgardproject/midgardmvc_helper_forms.git
services_dispatcher: midgard2
providers_component: midgardmvc
您还可以通过在 php.ini
中设置 midgardmvc.application_config
来定义应用程序配置文件的自定义位置。示例
midgardmvc.application_config=/etc/midgard2/example.yml
Midgard MVC 请求过程
- 引导
- 调用 Midgard MVC PHP 引导文件(《framework.php》)
- Midgard MVC 引导注册一个自动加载器(《midgardmvc_core::autoload()》)
- 前端控制器(《midgardmvc_core::get_instance()》)启动
- 前端控制器从配置提供程序加载配置
- 前端控制器加载配置中指定的组件提供程序
- 前端控制器加载配置中指定的层次结构提供程序
- 请求处理
- 请求对象填充了当前的 HTTP 请求参数
- 如果已加载的组件注册了任何内容,则会调用进程注入器。
- 请求对象使用层次提供者来确定处理请求的组件。
- 请求对象加载必要的组件。
- 前端控制器将请求对象传递给分发器。
- 分发器将请求分发给组件控制器类,并传递请求对象。
- 组件控制器类执行并将数据设置到请求对象中。
- 模板化
- 前端控制器加载配置中指定的模板提供者。
- 如果已加载的组件注册了任何内容,则会调用模板注入器。
- 前端控制器确定与请求一起使用的模板。
- 前端控制器使用模板提供者生成请求输出。
- 请求输出发送到浏览器。
Midgard MVC前端控制器
Midgard MVC前端控制器 midgardmvc_core
负责捕获HTTP请求并确保它得到处理和模板化。前端控制器实现单例模式,意味着一次只有一个Midgard MVC前端控制器可用。
前端控制器可通过以下方式访问:
$mvc = midgardmvc_core::get_instance();
Midgard MVC分发器
Midgard MVC分发器接收请求对象,实例化和调用必要的组件和控制器,并调用它们的操作方法。
分发器可通过以下方式访问:
$dispatcher = midgardmvc_core::get_instance()->dispatcher;
$dispatcher->dispatch($request);
根据调用(如果有的话)的控制器和操作方法,这将返回包含一些新数据的请求对象,或者引发异常。
组件结构
组件是运行在Midgard MVC内部的职能模块。它通常与特定的Midgard MVC节点关联运行,但也可以将其附加到另一个组件的节点上运行。
- component_name
manifest.yml
:组件的包清单,路由和信号监听器注册。- 配置
defaults.yml
:组件的默认配置,以名称-值对的形式。
- 控制器
controllername.php
:组件的控制器类。
- 模型
classname.xml
:组件使用的Midgard Schema,注册类型classname
。- 视图
viewname.xml
:组件使用的Midgard View,注册视图viewname
。
classname.php
:扩展Midgard Schema的PHP类。
- 服务
authentication.php
:Midgard MVC认证服务的组件特定实现。
- 模板
templatename.xhtml
:组件使用的TAL模板,名为templatename
。
路由
组件的个别路由(或视图)在组件清单中定义。Midgard MVC从路由定义中构建路由对象。
路由映射了URL和相应的控制器类以及操作方法。
最小路由定义
route_identifier:
- path: '/some/url'
- controller: controller class
- action: action name
- template_aliases:
- root: name of template used when "root" is included
- content: name of template used when "content" is included
路由匹配
Midgard MVC有几种方法将请求匹配到路由。匹配由向请求类的工厂方法提供意图来处理。
- 显式匹配
- 在显式匹配中,我们知道组件实例、路由标识符和参数。
- 隐式匹配
- 在隐式匹配中,我们知道一个或多个以下内容:
- 路由标识符和参数
- URL
- 组件名称
- 现有的请求对象
- 在隐式匹配中,我们知道一个或多个以下内容:
URL模式
路由路径(在给定层次节点下)由路由的路径属性定义。路径遵循 URL模式规范。
可以使用以下方式在URL模式中使用变量:
- 命名变量
-path: '/{$foo}'
- 带有请求
/bar
,控制器会以$args['foo'] = 'bar'
被调用。
- 命名和类型变量
-path: '/latest/{int:$number}'
- 使用请求
/latest/5
,控制器会通过$args['number'] = 5
被调用
- 未命名的参数
- -path
'/file/@'
- 使用请求
/file/a/b
,控制器会通过$args['variable_arguments'] = array('a', 'b')
被调用
- -path
限制路由可用性
如果您希望路由仅在运行在网站根目录(/
)时才可访问,您可以在该路由定义中添加以下内容
routes:
some_route:
root_only: true
另一种选择是确保路由仅在子请求(dynamic_load
和 dynamic_call
)中使用时才可访问,并且不能直接通过浏览器访问。这可以通过以下方式在路由定义中实现
routes:
some_route:
subrequest_only: true
处理路由注册的组件提供者将确保不符合这些限制的路由不会为请求进行注册
控制器的工作原理
控制器是一个包含一个或多个与组件路由定义匹配的操作的PHP类。当被调用时,Midgard MVC调度器将加载组件,实例化路由定义中指定的控制器类,传递请求对象和请求数据数组的引用,并最终调用与路由对应的操作方法,传递请求中的参数。
然后控制器将执行所需的任何处理或数据检索。控制器想要传递给模板的任何内容都应该添加到数据数组中。如果发生任何错误,控制器应抛出异常。
操作是控制器类中的公共方法。操作方法使用模式 <HTTP verb>_<action name>
命名,例如 get_article()
或 post_form()
。操作方法将接收包含数组的参数,作为可能的URL参数。
这里有一个简单的例子。来自 net_example_calendar/manifest.yml
的路由定义
show_date:
- path: '/date'
- controller: net_example_calendar_controllers_date
- action: date
- template_aliases:
- content: show-date
控制器类 net_example_calendar/controllers/date.php
<?php
class net_example_calendar_controllers_date
{
public function __construct(midgardmvc_core_request $request)
{
$this->request = $request;
}
public function get_date(array $args)
{
$this->data['date'] = strftime('%x %X');
}
}
显示时间
控制器运行后,MVC执行中的下一阶段是模板化。使用了两个级别的模板
- 模板入口点:包含一个通过
<mgd:include>content</mgd:include>
的内容区域的“整个页面”模板 - 内容入口点:页面中的“内容区域”,如主模板中定义的
每个路由定义可以决定在每个级别使用哪些模板。如果路由想要覆盖整个站点的模板,则应定义自己的模板入口点;如果只想在内容区域显示某些内容,则应定义自己的内容入口点。
模板通过给它们命名来定义。例如,用于显示当前日期的模板可以命名为 show-date
。当MVC进入模板化阶段时,这可以在路由中定义
show_date:
- path: '/date'
- controller: net_example_calendar_date
- action: date
- template_aliases:
- content: show-date
当路由的模板化阶段发生时,MVC将寻找模板堆栈中的此类元素。模板堆栈是运行在当前请求中的组件列表。首先MVC在当前组件中寻找元素,如果在那里找不到,它就会在堆栈中查找
- 当前运行的组件
templates
目录 - 注入到模板堆栈中的任何组件的
templates
目录 - Midgard MVC核心
templates
目录
第一个匹配的模板元素将被使用并执行,通过TAL。组件返回的数据将作为 current_component
变量暴露给TAL。在我们的日期示例中,模板可能只是一个具有以下内容的 net_example_calendar/templates/show-date.xhtml
文件
<p>Current date is <span tal:content="current_component/date">5/8/1999 01:00</span></p>
请求隔离和子请求的创建
Midgard MVC支持在同一个网页内处理多个请求。例如,页面的主要内容可以从一个请求中提供,然后侧边栏中的新闻列表可以由子请求处理。
由于这意味着可能存在多个路由、控制器和模板在相同的PHP执行中运行,因此每个请求必须在PHP变量作用域内进行隔离。为了实现这一点,原则是将所有请求特定的信息存储在传递给前端控制器、调度器和实际控制器的请求对象中,它们实际上是无状态的。例如,调度器的dispatch
方法或前端控制器的template
方法可以在同一PHP执行中多次运行。
在Midgard MVC执行的任何阶段,您都可以以下方式发起子请求
<?php
// Set up intent, for example a hierarchy node, node URL or component name
$intent = '/myfolder/date';
// Get a Request object based on the intent
$request = midgardmvc_core_request::get_for_intent($intent);
// Process the Request
midgardmvc_core::get_instance()->dispatcher->dispatch($request);
// Use the resulting data
$component_data = $request->get_data_item('current_component');
echo $component_data['date'];
?>
为了方便起见,模板类中有两个子请求处理的辅助函数。动态调用将执行子请求并返回其数据
$data = midgardmvc_core::get_instance()->templating->dynamic_call($intent, $route_id, $route_args);
动态加载将执行子请求并返回其模板输出
$content = midgardmvc_core::get_instance()->templating->dynamic_load($intent, $route_id, $route_args, true);
错误处理
Midgard MVC致力于鼓励安全的PHP编程实践。因此,在框架下运行的任何PHP代码都使用E_ALL错误报告级别。这意味着使用未分配的变量和其他类似的潜在错误来源将导致在页面上显示PHP错误。
实际的应用级错误处理是通过异常来完成的。在任何应用执行阶段,您都可以通过抛出适当情况的异常来停止处理。这将导致显示错误页面,并将错误记录到MVC中。
与Midgard MVC一起使用的典型异常有
midgardmvc_exception_notfound
:请求的内容找不到。显示HTTP 404错误页面。midgardmvc_exception_unauthorized
:用户尝试执行未经授权的操作。显示带有登录可能性的HTTP 401错误页面。midgardmvc_exception_httperror
:其他类型的HTTP错误。应将所需的HTTP错误代码作为异常构造函数的第二个参数提供。
在您的控制器中抛出适当异常的示例
public function post_comment(array $args)
{
$article = new net_example_article($args['article']);
if (!$article->guid)
{
throw new midgardmvc_exception_notfound("Article {$args['article']} could not be found");
}
if (!midgardmvc_core::get_instance()->authorization->can_do('mgd:create', $article))
{
throw new midgardmvc_exception_unauthorized("You are not allowed to comment article {$article->title}");
}
// Implement saving a new comment to the article here
}
保存路由状态
注意:状态保存在Midgard MVC中尚未实现。
尽管Web本身是无状态的,但在Midgard MVC中,一个路由可以通过两种不同的方式保存其状态
- 在请求之间保存路由的请求数据
- 在请求之间保存路由的输出
如果从保存的状态中找到路由数据,则控制器操作将使用预填充的数据调用。控制器操作可以据此使用或忽略它。存储的路由数据典型用途是避免不必要的Midgard数据库查询来检索已可用信息。
如果从保存的状态中找到路由输出,则Midgard MVC将直接将此输出返回给用户,控制器操作或模板将不会运行。
状态受众
由于可以有多个用户(具有不同的权限和首选项)与路由进行交互,因此路由可以声明它想要为哪些受众保存其状态。支持选项有
- public
- 同一状态信息用于该路由的所有用户
- private
- 状态信息分别存储和使用于每个Midgard用户
- session
- 状态信息分别存储和使用于每个PHP会话。这基本上与
private
相同,但也适用于非认证用户
- 状态信息分别存储和使用于每个PHP会话。这基本上与
在路由定义中传达路由的受众
route_identifier:
- cache_audience: public
如果没有定义受众,Midgard MVC将把路由视为private
。
状态无效化
在保存状态时,路由必须通知Midgard MVC与数据关联的标签或过期日期,或两者都有。
Midgard MVC将在过期后自动无效化所有保存的状态信息。
状态标签是与状态相关联的关键词的自由列表,并且它们的无效化必须由组件开发者自行处理。然而,多个组件可以使用相同的状态标签,并且使其中一个标签无效将使所有使用该标签的路由的状态无效。