在传统的前后端不分离的项目中,页面由后端通过模板进行渲染,在这之上又衍生出了MVC架构,这就涉及到了请求转发和重定向的问题。本文主要介绍下这部分内容,当然近几年开始流行前端MVVM框架,对于后端的编码可以说极大的简化了,这部分内容在Ajax相关章节介绍。
所谓的「请求转发」其实是Web开发的后端概念,指将一个HTTP请求从一个业务逻辑的处理单元交给下一个处理单元进行处理,在Java中这非常常见,因为所有的Java开发者从一开始学习JavaWeb就是用的MVC模式进行开发, 所有本世纪的Java书都会明确告诉你只用JSP的被称为「Model 1」的开发模式是上个世纪的做法,三层架构和表现层MVC是最基本的要求。在MVC架构中,客户端的请求被控制器(C)处理后,可以交给下一个控制器,或者交给视图(V)进行展示,即使最简陋的JavaWeb程序,也是Servlet+JSP。
如果PHP想要实现MVC开发模式,最简单的做法就是用PHP实现的控制器,将请求的处理结果「转发」给一个同样用PHP实现的视图。
然而,转念一想,PHP中似乎并没有请求转发这个概念!!!
其实所谓的「请求转发」说白了就是传数据,我们自己传就行了。具体看之后的例子。
Web开发中,重定向是个重要的概念,有许多场景都会用到重定向:
重定向是后端通过发送302
码和Location
报头实现的,具体见HTTP协议相关的内容。在PHP中,实现重定向非常简单:
<?php
header("Location:index.php");
PHP会自动帮我们加上302
状态码。
下面例子中,使用MVC架构,和上面提到了请求转发和重定向的概念,实现登录功能。用户名和密码校验通过,就重定向到欢迎页面,校验不通过,就转发回登录页,显示错误信息。
这个例子是比较复杂的,其实这样一些,我们也可以顺便反思一下Java中的过度设计。
model/User.php
<?php
class User
{
public $username;
public $password;
public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}
}
User是实体类,包含用户的信息,这里比较简单,包含的信息就是用户的登录名和密码。用户登录后,我们会把这个类实例化,存在Session中。实际开发中,如果是中大型的项目,一般是通过ORM框架,将这个类映射到数据库表中,然后查询出来的。
view/login.php
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="LoginController.php" method="post">
<fieldset>
<input type="text" name="username"/>
<input type="password" name="password"/>
<input type="submit" value="登录"/>
<div style="color: red;">
<?php
if(isset($errMsg) && $errMsg != null)
{
echo $errMsg;
}
?>
</div>
</fieldset>
</form>
</body>
</html>
上面是登录页面的视图,也就是MVC中的V,我们可以看到$errMsg
其实是控制器传给视图的信息,其值是登录报错信息。我们用isset
进行了判断,因为我们首次访问这个页面,控制器是不会给我们报错信息的,因此根本不会设置$errMsg
这个变量。
controller/LoginController.php
<?php
class LoginController
{
function handleGetRequest()
{
// 请求登录页,没有处理动作
return null;
}
function handlePostRequest()
{
// 处理登录表单
$username = $_POST["username"];
$password = $_POST["password"];
if($username == "root" && $password == "123456")
{
// 登录成功,设置session,重定向到登录页
session_start();
require "../model/User.php";
$user = new User($username, $password);
$_SESSION["user"] = $user;
header("Location:IndexController.php");
return null;
}
else
{
return "用户名或密码错误";
}
}
}
$controller = new LoginController();
if($_SERVER["REQUEST_METHOD"] == "GET")
{
$controller->handleGetRequest();
require "../view/login.php";
}
else if($_SERVER["REQUEST_METHOD"] == "POST")
{
$errMsg = $controller->handlePostRequest();
require "../view/login.php";
}
上面是登录功能的控制器,尽管我们将其封装成了类,但是简单起见,我们还是直接在下面进行了类的实例化和调用,如果你觉得不优雅,可以把这部分代码提出来,单独封装成组件。
值得注意的是,我们在控制器中写了两个方法,handleGetRequest
和handlePostRequest
,分别处理GET请求和POST请求,GET请求对应获取页面,POST请求对应表单处理。我们可以看到登录表单的处理逻辑,如果密码校验成功,就设置Session然后重定向到首页,如果校验失败,就返回报错信息,这样不进行重定向,我们又include
了登录页面的视图,那么显示给用户的就是登录页面的视图和控制器返回的报错信息。
view/index.php
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<table>
<caption>商品</caption>
<tr>
<td>惠普暗影精灵2Pro</td>
<td><?php echo $result["product1"] ?></td>
</tr>
<tr>
<td>神舟战神GX8</td>
<td><?php echo $result["product2"] ?></td>
</tr>
</table>
</body>
</html>
上面是主页的视图,简单起见,我们仅仅在页面上显示了两个商品信息。
controller/IndexController.php
<?php
class IndexController
{
function handleGetRequest()
{
session_start();
if($_SESSION["user"] == null)
{
echo $_SESSION["user"];
header("Location:LoginController.php");
return null;
}
else
{
// 假装是从数据库中查出来的一些要展示在首页的数据
$db = array();
$db["product1"] = 7000;
$db["product2"] = 9000;
return $db;
}
}
}
$controller = new IndexController();
$result = $controller->handleGetRequest();
require "../view/index.php";
上面是主页的控制器,和登录控制器逻辑相比,简单了一点,主页的控制器我么只处理GET请求,返回的就是主页。但是,为了实现用户登录才能访问主页,我们进行了Session的判断,如果用户未登录,就不能访问主页,而是重定向到登录页。主页中的数据如果是实际开发中,一般都是从数据库中查出来的,这里我们就不连接数据库了,直接用代码进行模拟。
从上面例子中,我们可以发现,使用MVC会增加不少的代码量,尤其是各种数据封装和传递,如果没有一个合适的框架,手写起来是比较麻烦的。因此,使用原生PHP开发时,架构的设计也要适可而止,根据适用场景选择合适的代码架构,并没有一个通用的解决方案,这也是Java总被喷复杂臃肿的原因。