PHP MVC - 为什么我的路由控制器在实时服务器上给出 404

PHP MVC - Why does my route controller give 404s when on live server

我一直在阅读一本书,该书通过使用 PHP 和 MySQL 的用户控制应用程序解释了 MVC。据我所知,我的问题是当我使用 localhost 时我的 MVC 路由完美运行,但是当我将相同的代码应用于我的提供商服务器上的相同结构根目录时 - 它加载索引页面,然后加载控制器只是为其他所有内容抛出 404。我的登录表单也没有传递 controller/method,所以它可以通过 $_POST 检索。这一切都在本地完美运行,只是似乎落在了网络服务器上。 Composer 设置正确,我的 PHP 版本在本地和网络服务器上是 7.4。我已经用尽了 Internet 上的资料,但实际上却碰壁了。我将把所有相关的代码片段放在下面,如果有任何帮助,我们将不胜感激!

index.php (localhost > webroot)

<?php
//define a directory separator e.g. / or \ depending on the machine
defined('DS') || define('DS', DIRECTORY_SEPARATOR);
define('APPDIR', realpath(__DIR__.'/../app/') .DS);
define('SYSTEMDIR', realpath(__DIR__.'/../system/') .DS);
define('PUBLICDIR', realpath(__DIR__) .DS);
define('ROOTDIR', realpath(__DIR__.'/../') .DS);

//initiate config
$config = App\Config::get();

new System\Route($config);

Config.php(本地主机 > 应用程序)

<?php
namespace App;

use App\Helpers\Session;

class Config {
    public static function get() {

        //turn on output buffering
        ob_start();

        //turn on sessions
        Session::init();
        
        return [

            //set the namespace for routing
            'namespace' => 'App\Controllers\',

            //set the default controller
            //set to Home for Default view
            'default_controller' => 'admin',

            //set default method
            'default_method' => 'index',

            //database credentials
            'db_type' => 'mysql',
            'db_host' => 'localhost',
            'db_name' => '***',
            'db_username' => '***',
            'db_password' => '***',
        ];
    }
}

Route.php(本地主机 > 系统)

<?php
namespace System;

use System\View;

class Route {

    //construct method expects parameter called $config
    public function __construct($config) {

        //hold an array from the requested route (in the form of /page/requested)
        //when explode is run it finds forward slash in the requested URI (made available by $_SERVER)
        $url = explode('/', trim($_SERVER['REQUEST_URI'], '/'));

        //controller uses a ternary operator to check if 0 index of $url exists
        //otherwise default_controller will be used (defined in the config class)
        $controller = !empty($url[0]) ? $url[0] : $config['default_controller'];// Home/Admin

        //method checks for the existence of a $url[1] - or again defaults from config class
        $method = !empty($url[1]) ? $url[1] : $config['default_method']; 

        $args = !empty($url[2]) ? array_slice($url, 2) : array();

        $class = $config['namespace'].$controller;

        //check it the class exists
        if (! class_exists($class)) {
            return $this->not_found();
        }

        //check if the method exists
        if (! method_exists($class, $method)) {
            return $this->not_found();
        }

        //create an instance of the controller
        $classInstance = new $class;

        call_user_func_array(array($classInstance, $method), $args);
    }

    //if the class or method is not found - return a 404
    public function not_found() {
        $view = new View();
        return $view->render('404');
    }
}

Admin.php(本地主机 > 应用 > 控制器)

<?php

namespace App\Controllers;

use System\BaseController;
use App\Helpers\Session;
use App\Helpers\Url;
use App\Models\User;

class Admin extends BaseController {

    //set class local variable
    protected $user;

    //initialise the User Model by calling new User();
    public function __construct() {
        parent::__construct();

        $this->user = new User();
    }

    //check if logged in
    public function index() {

        //if they are not logged in, redirect to the login method
        //'logged_in' comes from Session::set method
        if (! Session::get('logged_in')) {
            Url::redirect('/admin/login');
        }

        //will set the title for the browser window
        $title = 'Dashboard';

        $this->view->render('admin/auth/login', compact('title'));
    }

    public function login () {

        //check if theres a session in play
        if (Session::get('logged_in')) {
            Url::redirect('/admin');
        }

        //create empty errors array
        $errors = [];

        //check if form has been submitted by checking if $_POST array contains an object called submit
        //$_POST comes from the admin>auth>login.php form
        if (isset($_POST['submit'])) {

            echo "Submitted";
            exit();

            //htmlspec - security measure, stops script tags from being able to be executed(renders as plaintext)
            $username = htmlspecialchars($_POST['username']);
            $password = htmlspecialchars($_POST['password']);

            //call built in function password_verify
            if (password_verify($password, $this->user->get_hash($username)) == false) {
                $errors[] = "Wrong username or password";
            }

            //count the errors, if theres none you can get the data and set the session using Session Helper
            if (count($errors) == 0) {

                //logged in
                $data = $this->user->get_data($username);

                Session::set('logged_in', true);
                Session::set('user_id', $data->id);

                //redirect the user to the admin index page
                Url::redirect("/admin");

            }

        }

        //set the title
        $title = 'Login';

        $this->view->render('admin/auth/login', compact('title', 'errors'));

    }

login.php (localhost > app > views > admin > auth)

<?php
//include header
include(APPDIR.'views/layouts/header.php');

use App\Controllers\Admin;
?>

<div class="wrapper well">

    <!--wrapper class will be used to position the DIV-->

    <!--include errors to catch any errors or messages -->
    <?php include(APPDIR.'views/layouts/errors.php');?>

    <!--form will have a POST method to send contents to an ACTION URL -->
    <!-- Admin is the class, and login is the method to be called in System\Route-->
    <form action="/admin/login" method="post">

    <h1>Login</h1>

    <!--User Input -->
    <div class="control-group">
        <label class="control-label" for="username"> Username</label>
        <input class="form-control" id="username" type="text" name="username" />
    </div>

    <!--Password Input -->
    <div class="control-group">
        <label class="control-label" for="password"> Password</label>
        <input class="form-control" id="password" type="text" name="password" />
    </div>

    <br>
    <!-- Submit button -->
    <p class="pull-left"><button type="submit" class="btn btn-sm btn-success" name="submit" value="submit">Login</button></p>

    <!-- Reset option-->
    <p class="pull-right"><a href="/admin/reset">Forgot Password</a></p>

    <!-- clear floats -->
    <div class="clearfix"></div>

    </form>

</div>

<!-- footer include -->
<?php include(APPDIR.'views/layouts/footer.php');?>

.htaccess(位于 index.php)

# Disable directory snooping
Options -Indexes

<IfModule mod_rewrite.c>

    RewriteEngine On
    RewriteBase /

    # Uncomment the rule below to force HTTPS (SSL)
    RewriteCond %{HTTPS} !on
    #RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

    # Force to exclude the trailing slash
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} (.*)/$
    RewriteRule ^(.+)/$  [R=307,L]

    # Allow any files or directories that exist to be displayed directly
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d

    RewriteRule ^(.*)$ index.php? [QSA,L]


    ErrorDocument 404 /errors/not-found.html
</IfModule>

您需要告诉网络服务器通过应用程序的主入口点 (index.php) 路由所有请求。例如,以下是 Laravel 为最常见的 Web 服务器软件(apache 和 nginx)提供的配置。

# APACHE conf (.htaccess)
<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews -Indexes
    </IfModule>

    RewriteEngine On

    # Handle Authorization Header
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} (.+)/$
    RewriteRule ^ %1 [L,R=301]

    # Send Requests To Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

# NGINX conf (app.conf)
server {
    listen 80;
    index index.php index.html;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/public;
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass coe_da_app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
    location / {
        try_files $uri $uri/ /index.php?$query_string;
        gzip_static on;
    }
}

非常感谢 Prieber 为我指明了正确的方向。我的问题的解决方案确实是网络服务器问题。事实证明,我的主机使用 IIS,因此我需要改为配置 web.config。下面的代码在我的标签里,我已经走了!

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="Imported Rule 1" stopProcessing="true">
                    <match url="^" ignoreCase="false" />
                    <conditions logicalGrouping="MatchAll">
                        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
                    </conditions>
                    <action type="Rewrite" url="index.php" />
                </rule>
            </rules>
        </rewrite>
    </system.webServer>
</configuration>