├── 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 |
6 | Verified if the path = $absolute_path ?> exists.
7 | Verified if there is an index within = $absolute_path ?> .
8 | Verified if there is an index in the document root
9 | = $config['root'] ?> to do an URL Rewrite.
10 |
11 |
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 |
= $contents ?>
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 | Type
9 | = $function_order_link('filename', 'Name') ?>
10 | = $function_order_link('size', 'Size') ?>
11 | = $function_order_link('owner', 'Owner') ?>
12 | = $function_order_link('group', 'Group') ?>
13 | = $function_order_link('perms', 'Permissions') ?>
14 | = $function_order_link('mTime', 'Modified') ?>
15 | Options
16 |
17 |
18 |
19 |
20 |
21 | This directory is empty.
22 |
23 |
24 |
27 |
28 |
37 |
38 |
39 | = $path['type'] ?>
40 |
41 |
42 | = $path['filename'] ?>
43 |
44 | = $path['size'] ?>
45 | = $path['owner'] ?>
46 | = $path['group'] ?>
47 | = $path['perms'] ?>
48 | = $path['mTime'] ?>
49 |
50 | Show
51 |
52 |
53 |
54 |
55 |
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 | = $title ?>
227 |
228 | PHP = phpversion() ?> built-in web server -
229 | info = date('r') ?>
230 |
231 |
232 |
233 |
234 |
235 | = 'php-server v' . $_SERVER['PHPSERVER_VERSION'] ?>
236 |
237 |
238 |
279 |
280 |
281 |
--------------------------------------------------------------------------------