转发和重定向

在传统的前后端不分离的项目中,页面由后端通过模板进行渲染,在这之上又衍生出了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开发中,重定向是个重要的概念,有许多场景都会用到重定向:

  1. 登录成功,重定向到主页
  2. 将未登录的用户对主页发起的请求重定向到登录页
  3. 对未授权的操作重定向到错误页,实现对接口的保护

重定向是后端通过发送302码和Location报头实现的,具体见HTTP协议相关的内容。在PHP中,实现重定向非常简单:

<?php
header("Location:index.php");

PHP会自动帮我们加上302状态码。

MVC实现登录功能

下面例子中,使用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";
}

上面是登录功能的控制器,尽管我们将其封装成了类,但是简单起见,我们还是直接在下面进行了类的实例化和调用,如果你觉得不优雅,可以把这部分代码提出来,单独封装成组件。

值得注意的是,我们在控制器中写了两个方法,handleGetRequesthandlePostRequest,分别处理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总被喷复杂臃肿的原因。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap