├── bin └── php-server ├── src ├── pages │ ├── favicon.png │ ├── error-404.php │ ├── show-file.php │ ├── default.php │ └── _template.php ├── banner.txt ├── server_config.php ├── server_router.php ├── server.php └── functions.php ├── composer.json ├── LICENSE └── README.md /bin/php-server: -------------------------------------------------------------------------------- 1 | ../src/server.php -------------------------------------------------------------------------------- /src/pages/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natanfelles/php-server/HEAD/src/pages/favicon.png -------------------------------------------------------------------------------- /src/banner.txt: -------------------------------------------------------------------------------- 1 | ____ _ _ ____ ____ 2 | | _ \| | | | _ \ / ___| ___ _ ____ _____ _ __ 3 | | |_) | |_| | |_) | \___ \ / _ \ '__\ \ / / _ \ '__| 4 | | __/| _ | __/ ___) | __/ | \ V / __/ | 5 | |_| |_| |_|_| |____/ \___|_| \_/ \___|_| 6 | 7 | -------------------------------------------------------------------------------- /src/pages/error-404.php: -------------------------------------------------------------------------------- 1 |

Document root

2 |
3 |

You are seeing this because the URL really could not be responded.

4 |

That was the order of our attempts:

5 | 12 |

Therefore, this URL does not really exist.

13 |
14 | -------------------------------------------------------------------------------- /src/pages/show-file.php: -------------------------------------------------------------------------------- 1 | '', ' ' => ' ']); 7 | $contents = str_replace(['
', '
'], "\n", $contents); 8 | } 9 | } else { 10 | $contents = '
' . htmlentities($contents) . '
'; 11 | } 12 | 13 | $function_parent_dir($relative_path); 14 | ?> 15 |
16 |
$c) { 18 | echo '' . ($line + 1) . ''; 19 | } 20 | ?>
21 |
22 |
23 | -------------------------------------------------------------------------------- /src/server_config.php: -------------------------------------------------------------------------------- 1 | PHP_BINARY, 9 | 'host' => 'localhost', 10 | 'port' => '8080', 11 | 'root' => getcwd(), 12 | 'autoindex' => true, 13 | 'index' => 'index.html index.php', 14 | 'error_reporting' => E_ALL, 15 | 'ini' => [ 16 | 'display_errors' => 1, 17 | 'display_startup_errors' => 1, 18 | 'max_execution_time' => 360, 19 | ], 20 | 'server' => [ 21 | 'PHPSERVER_VERSION' => '2.12.1', 22 | ], 23 | ]; 24 | 25 | if (isset($custom_config['root'])) { 26 | $custom_config['root'] = realpath($custom_config['root']); 27 | } 28 | 29 | return isset($custom_config) ? array_replace_recursive($default_config, $custom_config) 30 | : $default_config; 31 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "natanfelles/php-server", 3 | "description": "Fine-tuning on the PHP built-in web server.", 4 | "type": "library", 5 | "keywords": [ 6 | "server", 7 | "php-server", 8 | "webserver", 9 | "dev", 10 | "development", 11 | "document-root", 12 | "autoindex", 13 | "rewrite", 14 | "mod-rewrite", 15 | "devtools" 16 | ], 17 | "homepage": "https://github.com/natanfelles/php-server", 18 | "license": "MIT", 19 | "authors": [ 20 | { 21 | "name": "Natan Felles", 22 | "email": "natanfelles@gmail.com", 23 | "homepage": "https://natanfelles.github.io" 24 | } 25 | ], 26 | "support": { 27 | "issues": "https://github.com/natanfelles/php-server/issues", 28 | "source": "https://github.com/natanfelles/php-server" 29 | }, 30 | "require": { 31 | "php": ">=5.6.0" 32 | }, 33 | "bin": [ 34 | "bin/php-server" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Natan Felles 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/pages/default.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 37 | 38 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 |
TypeOptions
This directory is empty.
39 | 40 | 42 | 43 | 50 | Show 51 |
56 | -------------------------------------------------------------------------------- /src/server_router.php: -------------------------------------------------------------------------------- 1 | 8 | * @link https://github.com/natanfelles/php-server 9 | */ 10 | 11 | // Load our functions... 12 | require __DIR__ . '/functions.php'; 13 | 14 | /** 15 | * @var array $config Server config 16 | */ 17 | $config = require __DIR__ . '/server_config.php'; 18 | 19 | error_reporting($config['error_reporting']); 20 | 21 | foreach ($config['server'] as $key => $value) { 22 | $_SERVER[$key] = $value; 23 | } 24 | 25 | if (isset($_GET['php-server']) && $_GET['php-server'] === 'phpinfo') { 26 | $function_clean_vars(); 27 | phpinfo(); 28 | 29 | return true; 30 | } 31 | 32 | /** 33 | * Relative and Absolute directory path 34 | */ 35 | $relative_path = preg_replace('/\/$/', '', urldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH))); 36 | $absolute_path = $config['root'] . $relative_path; 37 | 38 | // Show file contents or goto listAll 39 | if (isset($_GET['php-server']) && $_GET['php-server'] === 'show') { 40 | if (is_dir($absolute_path)) { 41 | goto listAll; 42 | } 43 | 44 | $title = 'File ' . $relative_path; 45 | $page = 'show-file'; 46 | 47 | require __DIR__ . '/pages/_template.php'; 48 | 49 | $function_clean_vars(); 50 | return true; 51 | } 52 | 53 | // If is not a dir get the file content 54 | if (!is_dir($absolute_path) && is_file($absolute_path)) { 55 | $function_clean_vars(); 56 | 57 | return false; 58 | } 59 | 60 | // Run the index file 61 | $indexes = explode(' ', trim($config['index'])); 62 | 63 | foreach ($indexes as $index) { 64 | // Check if has an index inside the current dir 65 | if (is_file($absolute_path . '/' . $index)) { 66 | $_SERVER['PHPSERVER_INDEX'] = $absolute_path . '/' . $index; 67 | $function_clean_vars(); 68 | 69 | require $_SERVER['PHPSERVER_INDEX']; 70 | 71 | return true; 72 | } 73 | } 74 | 75 | // Rewrite - Check if has an index in the document root 76 | foreach ($indexes as $index) { 77 | if (is_file($config['root'] . '/' . $index)) { 78 | $_SERVER['PHPSERVER_INDEX'] = $config['root'] . '/' . $index; 79 | $function_clean_vars(); 80 | 81 | require $_SERVER['PHPSERVER_INDEX']; 82 | 83 | return true; 84 | } 85 | } 86 | 87 | listAll: 88 | 89 | // If has not any index and the called file or dir does not exist 90 | // means that we need a Error 404 91 | if (!file_exists($absolute_path)) { 92 | http_response_code(404); 93 | $title = 'Error 404'; 94 | $page = 'error-404'; 95 | 96 | require __DIR__ . '/pages/_template.php'; 97 | 98 | $function_clean_vars(); 99 | 100 | return true; 101 | } 102 | 103 | // If autoindex is disabled the paths list will not be showed 104 | if ((bool) $config['autoindex'] === false) { 105 | $function_clean_vars(); 106 | 107 | return true; 108 | } 109 | 110 | // File System iterator 111 | $filesystem = iterator_to_array(new FilesystemIterator($absolute_path)); 112 | 113 | // All child paths 114 | $paths = []; 115 | 116 | foreach ($filesystem as $pathname => $SplFileInfo) { 117 | if (!file_exists($SplFileInfo->getRealPath())) { 118 | continue; 119 | } 120 | 121 | if ($SplFileInfo->isDir()) { 122 | $fi = new FilesystemIterator($SplFileInfo->getRealPath()); 123 | } 124 | 125 | $paths[$pathname] = [ 126 | 'type' => $SplFileInfo->getType(), 127 | 'realPath' => $SplFileInfo->getRealPath(), 128 | 'filename' => $SplFileInfo->getFilename(), 129 | 'isDir' => $SplFileInfo->isDir(), 130 | 'size' => $SplFileInfo->isDir() ? iterator_count($fi) : $SplFileInfo->getSize(), 131 | 'owner' => $SplFileInfo->getOwner(), 132 | 'group' => $SplFileInfo->getGroup(), 133 | 'perms' => substr(sprintf('%o', $SplFileInfo->getPerms()), -4), 134 | 'mTime' => $SplFileInfo->getMTime(), 135 | 'href' => $function_path_href($SplFileInfo), 136 | ]; 137 | } 138 | 139 | $paths = $function_order_paths($paths); 140 | 141 | $title = 'Index of ' . (empty($relative_path) ? '/' : $relative_path); 142 | 143 | $page = 'default'; 144 | 145 | require __DIR__ . '/pages/_template.php'; 146 | 147 | $function_clean_vars(); 148 | -------------------------------------------------------------------------------- /src/server.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 9 | * @link https://github.com/natanfelles/php-server 10 | */ 11 | 12 | require __DIR__ . '/functions.php'; 13 | 14 | echo file_get_contents(__DIR__ . '/banner.txt'); 15 | 16 | // Create a new php-server config file 17 | if (in_array('new', $argv)) { 18 | if (is_file($file = getcwd() . '/php-server.ini')) { 19 | echo "The php-server.ini file already exists.\n"; 20 | } else { 21 | $handle = fopen($file, 'w+'); 22 | $ini = [ 23 | 'post_max_size' => ini_get('post_max_size'), 24 | 'upload_max_filesize' => ini_get('upload_max_filesize'), 25 | ]; 26 | $content = <<\n"; 70 | echo "Checking for the latest release..."; 71 | 72 | $info = @file_get_contents( 73 | 'https://api.github.com/repos/natanfelles/php-server/releases/latest', 74 | false, 75 | stream_context_create([ 76 | 'http' => [ 77 | 'user_agent' => 'php-server ' . $config['server']['PHPSERVER_VERSION'], 78 | 'timeout' => 15, 79 | ], 80 | ]) 81 | ); 82 | 83 | echo "\r"; 84 | 85 | if ($info && $info = json_decode($info)) { 86 | $version = ltrim($info->tag_name, 'v'); 87 | 88 | if ($version == $config['server']['PHPSERVER_VERSION']) { 89 | echo "The latest php-server version ({$version}) is running.\n"; 90 | } elseif ($version > $config['server']['PHPSERVER_VERSION']) { 91 | echo "A new release is available: Version {$version}.\n"; 92 | } 93 | } else { 94 | echo "The current php-server version is {$config['server']['PHPSERVER_VERSION']}.\n"; 95 | } 96 | 97 | echo "Check the repository at https://github.com/natanfelles/php-server\n"; 98 | exit; 99 | } 100 | 101 | $ini = ''; 102 | foreach ($config['ini'] as $key => $value) { 103 | if (is_array($value)) { 104 | foreach ($value as $val) { 105 | $ini .= " -d {$key}={$val}"; 106 | } 107 | } else { 108 | $ini .= " -d {$key}={$value}"; 109 | } 110 | } 111 | 112 | $options = getopt(null, ['php:', 'host:', 'port:', 'root:']); 113 | 114 | $php = isset($options['php']) ? $options['php'] : $config['php']; 115 | $host = isset($options['host']) ? $options['host'] : $config['host']; 116 | $port = isset($options['port']) ? $options['port'] : $config['port']; 117 | $root = isset($options['root']) ? $options['root'] : $config['root']; 118 | $router = __DIR__ . '/server_router.php'; 119 | 120 | echo $function_color('Version:') . " {$config['server']['PHPSERVER_VERSION']}\n"; 121 | echo $function_color('PHP Binary:') . " {$php}\n"; 122 | echo $function_color('Document Root:') . " {$root}\n"; 123 | echo $function_color('Web Address:') . " http://{$host}:{$port}\n"; 124 | echo $function_color('Date:') . ' ' . date('r') . "\n\n"; 125 | $root = strtr($root, [' ' => '\ ']); 126 | $router = strtr($router, [' ' => '\ ']); 127 | passthru("{$php}{$ini} -S {$host}:{$port} -t {$root} {$router}"); 128 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | 0 ? floor(log($size, 1024)) : 0; 22 | return number_format($size / pow(1024, $power), $power ? 2 23 | : 0, '.', ',') . ' ' . $units[$power]; 24 | }; 25 | 26 | /** 27 | * Clean all server variables 28 | */ 29 | $function_clean_vars = function () { 30 | foreach (array_keys($GLOBALS) as $var) { 31 | if (in_array($var, [ 32 | '_GET', 33 | '_POST', 34 | '_COOKIE', 35 | '_FILES', 36 | '_ENV', 37 | '_REQUEST', 38 | '_SERVER', 39 | 'GLOBALS', 40 | ])) { 41 | continue; 42 | } 43 | unset($GLOBALS[$var]); 44 | } 45 | }; 46 | 47 | /** 48 | * Get an order by link 49 | * 50 | * @param string $order_by 51 | * @param string $name 52 | * 53 | * @return string 54 | */ 55 | $function_order_link = function ($order_by, $name) { 56 | $href = $GLOBALS['relative_path'] . '?order_by=' . $order_by; 57 | $title = 'Order by ' . $name; 58 | $class = ''; 59 | $icon = ' '; 60 | if (isset($_GET['order_by']) && $_GET['order_by'] === $order_by || !isset($_GET['order_by']) && $order_by === 'filename') { 61 | $class = ' class="active asc"'; 62 | $icon = ' '; 63 | if (!isset($_GET['order'])) { 64 | $href .= '&order=asc'; 65 | $title .= ' asc'; 66 | $icon = ' '; 67 | $class = ' class="active desc"'; 68 | } 69 | } 70 | 71 | return "{$name}{$icon}\n"; 72 | }; 73 | 74 | $function_path_href = function ($SplFileInfo) { 75 | $q = ''; 76 | if ($SplFileInfo->isDir()) { 77 | if (isset($_GET['order_by']) && in_array($_GET['order_by'], [ 78 | 'filename', 79 | 'size', 80 | 'owner', 81 | 'group', 82 | 'perms', 83 | 'mTime', 84 | ])) { 85 | $q .= '?order_by=' . $_GET['order_by']; 86 | if (isset($_GET['order']) && in_array($_GET['order'], ['asc', 'desc'])) { 87 | $q .= '&order=' . $_GET['order']; 88 | } 89 | } 90 | } 91 | 92 | return $GLOBALS['relative_path'] . '/' . $SplFileInfo->getFilename() . $q; 93 | }; 94 | 95 | $function_order_paths = function ($paths) { 96 | array_multisort(array_keys($paths), SORT_NATURAL | SORT_FLAG_CASE, $paths); 97 | 98 | if (isset($_GET['order_by']) && in_array($_GET['order_by'], [ 99 | 'filename', 100 | 'size', 101 | 'owner', 102 | 'group', 103 | 'perms', 104 | 'mTime', 105 | ])) { 106 | usort($paths, function ($path1, $path2) { 107 | $order_by = $_GET['order_by']; 108 | 109 | if ($path1[$order_by] === $path2[$order_by]) { 110 | return 0; 111 | } 112 | 113 | return $path1[$order_by] < $path2[$order_by] ? -1 : 1; 114 | }); 115 | } 116 | 117 | if (isset($_GET['order']) && $_GET['order'] === 'asc') { 118 | $paths = array_reverse($paths); 119 | } 120 | 121 | $dirs = []; 122 | $files = []; 123 | 124 | foreach ($paths as $path) { 125 | $path['mTime'] = date('Y-m-d H:i:s', $path['mTime']); 126 | $path['size'] = $path['isDir'] 127 | ? $path['size'] . ' item' . ($path['size'] < 2 ? '' : 's') 128 | : $GLOBALS['function_size_conversion']($path['size']); 129 | 130 | if ($path['isDir']) { 131 | $dirs[] = $path; 132 | } else { 133 | $files[] = $path; 134 | } 135 | } 136 | 137 | return array_merge($dirs, $files); 138 | }; 139 | 140 | $function_color = function ($text) { 141 | return "\033[0;32m$text\033[0m"; 142 | }; 143 | 144 | $function_parent_dir = function ($relative_path) { 145 | if ($relative_path): 146 | $query = isset($_GET['php-server']) ? '?php-server=' . $_GET['php-server'] : ''; 147 | $relative_path = explode('/', $relative_path); 148 | array_pop($relative_path); 149 | $relative_path = implode('/', $relative_path); 150 | $relative_path .= $relative_path ? $query : '/' . $query; 151 | ?> 152 |

Parent dir

153 | See release notes at: https://github.com/natanfelles/php-server/releases 12 | 13 | ## Installation 14 | 15 | ### Composer 16 | 17 | Open your terminal and run: 18 | 19 | ```sh 20 | composer global require natanfelles/php-server 21 | ``` 22 | 23 | Add the composer bin path to your *.bashrc*: 24 | 25 | ```sh 26 | echo 'export PATH="$PATH:$HOME/.config/composer/vendor/bin"' >> ~/.bashrc 27 | ``` 28 | 29 | Run: 30 | 31 | ```sh 32 | source ~/.bashrc 33 | ``` 34 | 35 | ### Manual 36 | 37 | Download and extract the *php-server* project folder. 38 | 39 | Add the php-server alias to your *.bashrc*: 40 | 41 | ```sh 42 | echo 'alias php-server="~/php-server/bin/php-server"' >> ~/.bashrc 43 | ``` 44 | 45 | Run: 46 | 47 | ```sh 48 | source ~/.bashrc 49 | ``` 50 | 51 | ## Config 52 | 53 | You can run the php-server in any folder of your operating system. 54 | 55 | Each time you run the *php-server* command, it will look up if there is a file named *php-server.ini* in the current directory. If found, your settings will override the default settings. 56 | 57 | A quick example file content is: 58 | 59 | ```ini 60 | php = PHP_BINARY 61 | host = localhost 62 | port = 8080 63 | root = ./public 64 | autoindex = true 65 | index = index.php 66 | error_reporting = E_ALL 67 | 68 | [ini] 69 | display_errors = 1 70 | display_startup_errors = 1 71 | max_execution_time = 360 72 | post_max_size = 200M 73 | upload_max_filesize = 200M 74 | 75 | [server] 76 | ENVIRONMENT = development 77 | ``` 78 | 79 | > You can use the command `php-server new` to create a new configuration file in the current directory. 80 | 81 | ### Explanation 82 | 83 | #### General Vars 84 | 85 | | Key | Default Value| Description | 86 | | --------------- | --- | --- | 87 | | php | [PHP_BINARY](http://php.net/manual/en/reserved.constants.php#constant.php-binary) | PHP binary path or command | 88 | | host | localhost | Server host | 89 | | port | 8080 | Server host port | 90 | | root | [getcwd()](http://php.net/manual/en/function.getcwd.php) | Document root. The location that will be the public root of your website. | 91 | | autoindex | true | Determines if the server will list directory contents if it does not find an index file. | 92 | | index | index.html index.php | The names of the index files separated by spaces. | 93 | | error_reporting | [E_ALL](http://php.net/manual/en/errorfunc.constants.php#errorfunc.constants.errorlevels.e-all) | Sets the [level of errors](http://php.net/manual/en/function.error-reporting.php) that will be reported. | 94 | 95 | #### Sections 96 | 97 | | Section | Description | 98 | | --- | --- | 99 | | ini | Used to set custom [php.ini directives](http://php.net/manual/en/ini.list.php). | 100 | | server | Used to set custom [Server and execution environment information](http://php.net/manual/en/reserved.variables.server.php). | 101 | 102 | Knowing this, just create (if necessary) a php-server.ini file, run the server and you're done. 103 | 104 | ## Run 105 | 106 | As you can see in the [config](#config). You can create a php-server.ini file to leave the settings of each project already pre-established. 107 | 108 | But, you can also simply run `php-server` and the server will already be available at [http://localhost:8080](http://localhost:8080). 109 | 110 | The php-server command can receive some parameters and they are: 111 | 112 | | Parameter | Description | 113 | | --- | --- | 114 | | --php | PHP binary path or command | 115 | | --host | Server host | 116 | | --port | Server host port | 117 | | --root | Document root | 118 | 119 | For example, to run the server on a different port: 120 | 121 | ```sh 122 | php-server --port 8081 123 | ``` 124 | 125 | Or, also with a different version of PHP than the default: 126 | 127 | ```sh 128 | php-server --php php8.4 --port 8081 129 | ``` 130 | 131 | Right. You get the idea. If you want to run on a different host you can add the host to the [hosts file](https://en.wikipedia.org/wiki/Hosts_(file)) of your operating system. 132 | 133 | ## Contribute 134 | 135 | Hello, how nice that you are reading this. 136 | 137 | If you have any idea to improve this project or something is not working as it should, do not hesitate to open an [issue](https://github.com/natanfelles/php-server/issues) and if you have solved the problem feel free to open a [Pull Request](https://github.com/natanfelles/php-server/pulls). 138 | -------------------------------------------------------------------------------- /src/pages/_template.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <?= htmlentities($title) ?> 6 | 7 | 200 | 201 | 202 | " 219 | . ($i === 0 ? '' : '/') 220 | . "{$dirs[$i]}"; 221 | } 222 | 223 | $title = $pre . ' /' . $breadcrumbs; 224 | } 225 | ?> 226 |

227 |

228 | PHP built-in web server - 229 | info 230 |

231 | 232 | 233 | 234 |

235 | 236 |

237 | 238 | 279 | 280 | 281 | --------------------------------------------------------------------------------