Running multiple local PHP web servers
Tags: php, tutorial, localdevelopment
Published: 2021-04-17 00:16:07

PHP is a both a front-end and back-end scripting language. If you've also used PHP for both ends of a full-stack application, you might relate to the frustration of looking for a free, high-quality local development tool for full-stack PHP developers. 

With JavaScript, you can start a server directly in code, choose the port, and rewrite the urls without hassle, using Node's core http module. 

Similarly, Java supports the feature through, for example, [Sun's NET HttpServer package](https://www.codeproject.com/tips/1040097/create-a-simple-web-server-in-java-http-server) and C# [as well](https://www.codeproject.com/tips/485182/create-a-local-server-in-csharp).

But PHP is different from other languages. To start up the built-in web server, you use the following command:

`php -S localhost:8000`

Unfortunately, this creates only a very basic server that cannot rewrite files or interact with a database easily. 

To resolve this, the recommended approach for hobbyists like myself is to use an Apache distribution:

- [XAMPP](https://www.apachefriends.org/download.html)
- [WAMPServer](https://www.wampserver.com/en/)
- LAMP (Linux)

The above are all tech stacks with PHP, MySQL, and Apache. The Apache server will read the `.htaccess` file before serving a page, which means you can make more user-friendly URL names. 

# The Problem

And now comes a problem. You can only run one server at a time using Apache, so let's say you choose the front-end. 

What do you do with the back-end? My initial reaction when trying this out myself was to use the PHP command for this. 

This works, only partly. The REST API also uses url rewrites on the live server, and I want to make it as close as possible so I can minimize or even eliminate "development mode" conditionals. 

# The Solution

The solution is to use a `router.php` file. Here's an example:

```
<?php

chdir(__DIR__);
$filePath = realpath(ltrim($_SERVER["REQUEST_URI"], '/'));
if ($filePath && is_dir($filePath)){
    // attempt to find an index file
    foreach (['index.php', 'index.html'] as $indexFile){
        if ($filePath = realpath($filePath . DIRECTORY_SEPARATOR . $indexFile)){
            break;
        }
    }
}
if ($filePath && is_file($filePath)) {
    // 1. check that file is not outside of this directory for security
    // 2. check for circular reference to router.php
    // 3. don't serve dotfiles
    if (strpos($filePath, __DIR__ . DIRECTORY_SEPARATOR) === 0 &&
        $filePath != __DIR__ . DIRECTORY_SEPARATOR . 'router.php' &&
        substr(basename($filePath), 0, 1) != '.'
    ) {
        if (strtolower(substr($filePath, -4)) == '.php') {
            // php file; serve through interpreter
            include $filePath;
        } else {
            // asset file; serve from filesystem
            return false;
        }
    } else {
        // disallowed file
        header("HTTP/1.1 404 Not Found");
        echo "404 Not Found";
    }
} else {
    // rewrite to our index file
    include __DIR__ . DIRECTORY_SEPARATOR . 'index.php';
}
```

I then ran `php -S localhost:8100 router.php` in the root directory of my API. 

# Conclusion

And _Voila!_ I was able to communicate between the front-end and back-end server without needing to deploy until I was ready!

Here is the StackOverflow question that helped to this solution: https://stackoverflow.com/questions/27381520/php-built-in-server-and-htaccess-mod-rewrites

I hope you found this post useful. Do you know any alternatives or easier ways to accomplish the same task? Leave a comment down below!