I’ve been going through a book which explains MVC through a user control application using PHP and MySQL. So as far as I can tell, my issue is that my MVC routing works perfectly when I use localhost, but when I apply the same code to the same structure root directories on my providers server – it loads the index page, but then the controller just throws 404s for everything else. My login form also isn’t passing the controller/method so it can be retrieved by $_POST. This all works perfectly locally, just seems to fall down on web server. Composer is setup correctly, my PHP version is 7.4 on local and web server. I’ve exhausted materials on the internet and really have just hit a brick wall with it. I’ll put all the relevant code snippets below, and any help would be greatly appreciated!
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 = AppConfig::get();
new SystemRoute($config);
Config.php (localhost > app)
<?php
namespace App;
use AppHelpersSession;
class Config {
public static function get() {
//turn on output buffering
ob_start();
//turn on sessions
Session::init();
return [
//set the namespace for routing
'namespace' => 'AppControllers\',
//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 (localhost > system)
<?php
namespace System;
use SystemView;
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 (localhost > app > Controllers)
<?php
namespace AppControllers;
use SystemBaseController;
use AppHelpersSession;
use AppHelpersUrl;
use AppModelsUser;
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 AppControllersAdmin;
?>
<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 SystemRoute-->
<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 (located with 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 ^(.+)/$ $1 [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?$1 [QSA,L]
ErrorDocument 404 /errors/not-found.html
</IfModule>
2
Answers
So thanks to Prieber for pointing me in the right direction. The solution to my problem was indeed a web server issue. As it turned out, my host uses IIS, so i needed to configure web.config instead. The following code in my <system.webServer> tags, and I was away!
You’ll need to tell the web server to route all requests through your application’s main entry point (index.php). As an example, here are the configs provided by Laravel for the most common web server software (apache and nginx).