Boilerplate code for starting any PHP webapp using built-in development server
This commit is contained in:
parent
2967d8beb4
commit
3857298a88
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/plugins/
|
||||||
|
/*.db
|
4
.htaccess
Normal file
4
.htaccess
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^/?(.*)$ index.php?path=/$1 [L,QSA]
|
175
index.php
Normal file
175
index.php
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
<?php
|
||||||
|
ini_set("display_errors", "off");
|
||||||
|
|
||||||
|
// For compatibility with PHP 5.3.0:
|
||||||
|
$varConstants = array(
|
||||||
|
array("JSON_HEX_TAG", 1),
|
||||||
|
array("JSON_HEX_AMP", 2),
|
||||||
|
array("JSON_HEX_APOS", 4),
|
||||||
|
array("JSON_HEX_QUOT", 8),
|
||||||
|
array("JSON_FORCE_OBJECT", 16),
|
||||||
|
array("JSON_NUMERIC_CHECK", 32),
|
||||||
|
array("JSON_UNESCAPED_SLASHES", 64),
|
||||||
|
array("JSON_PRETTY_PRINT", 128),
|
||||||
|
array("JSON_UNESCAPED_UNICODE", 256),
|
||||||
|
|
||||||
|
array("JSON_OBJECT_AS_ARRAY", 1),
|
||||||
|
array("JSON_BIGINT_AS_STRING", 2),
|
||||||
|
array("JSON_PARSE_JAVASCRIPT", 4),
|
||||||
|
|
||||||
|
array("JSON_ERROR_NONE", 0),
|
||||||
|
array("JSON_ERROR_DEPTH", 1),
|
||||||
|
array("JSON_ERROR_STATE_MISMATCH", 2),
|
||||||
|
array("JSON_ERROR_CTRL_CHAR", 3),
|
||||||
|
array("JSON_ERROR_SYNTAX", 4),
|
||||||
|
array("JSON_ERROR_UTF8", 5),
|
||||||
|
array("JSON_ERROR_RECURSION", 6),
|
||||||
|
array("JSON_ERROR_INF_OR_NAN", 7),
|
||||||
|
array("JSON_ERROR_UNSUPPORTED_TYPE", 8),
|
||||||
|
|
||||||
|
array("JSON_INVALID_UTF8_IGNORE", 1048576)
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($varConstants as $k => $v)
|
||||||
|
if (!defined($k))
|
||||||
|
define($k, $v);
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
ob_clean();
|
||||||
|
header("Content-Type: text/html");
|
||||||
|
|
||||||
|
$strResource = $_SERVER["REQUEST_URI"];
|
||||||
|
if (strlen($strResource) > 0)
|
||||||
|
$strResource = substr($strResource, 1);
|
||||||
|
|
||||||
|
$strPluginsDirectory = "plugins";
|
||||||
|
$varPaths = array(".");
|
||||||
|
|
||||||
|
if (is_dir($strPluginsDirectory))
|
||||||
|
foreach (scandir($strPluginsDirectory) as $strPluginName)
|
||||||
|
{
|
||||||
|
if ($strPluginName == ".") continue;
|
||||||
|
if ($strPluginName == "..") continue;
|
||||||
|
|
||||||
|
$strPluginDirectory = "{$strPluginsDirectory}/{$strPluginName}";
|
||||||
|
if (is_dir($strPluginDirectory))
|
||||||
|
$varPaths[] = "{$strPluginDirectory}";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($varPaths as $strPath)
|
||||||
|
{
|
||||||
|
$strTargetFilePath = "{$strPath}/{$strResource}";
|
||||||
|
|
||||||
|
if (is_file($strTargetFilePath))
|
||||||
|
{
|
||||||
|
$varMimeTypes = array(
|
||||||
|
array("/\.css$/", "text/css"),
|
||||||
|
array("/\.js$/", "application/javascript"),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($varMimeTypes as $varMimeType)
|
||||||
|
if (preg_match($varMimeType[0], $strTargetFilePath))
|
||||||
|
header("Content-Type: {$varMimeType[1]}");
|
||||||
|
|
||||||
|
ob_clean();
|
||||||
|
echo file_get_contents($strTargetFilePath);
|
||||||
|
ob_end_flush();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$varCollection = array(
|
||||||
|
"lib" => array(),
|
||||||
|
"init.php" => array(),
|
||||||
|
"head.php" => array(),
|
||||||
|
"header.php" => array(),
|
||||||
|
"footer.php" => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($varPaths as $strPath)
|
||||||
|
{
|
||||||
|
$x = null;
|
||||||
|
|
||||||
|
if (is_dir($x = "{$strPath}/lib"))
|
||||||
|
$varCollection["lib"][] = $x;
|
||||||
|
|
||||||
|
foreach (["init.php", "head.php", "header.php", "footer.php"] as $strScript)
|
||||||
|
if (is_file($x = "{$strPath}/{$strScript}"))
|
||||||
|
$varCollection[$strScript][] = $x;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($varCollection["lib"] as $strLibraryDirPath)
|
||||||
|
foreach (scandir($strLibraryDirPath) as $strLibraryFileName)
|
||||||
|
{
|
||||||
|
$strLibraryFilePath = "{$strLibraryDirPath}/{$strLibraryFileName}";
|
||||||
|
if (preg_match("/\.php$/", $strLibraryFilePath))
|
||||||
|
if (is_file($strLibraryFilePath))
|
||||||
|
require $strLibraryFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
$strBodyTagAttributes = "";
|
||||||
|
|
||||||
|
function requireAll($strScriptName)
|
||||||
|
{
|
||||||
|
global $varCollection;
|
||||||
|
foreach ($varCollection[$strScriptName] as $strScript)
|
||||||
|
{
|
||||||
|
if (is_file($strScript))
|
||||||
|
{
|
||||||
|
error_log($strScript);
|
||||||
|
require $strScript;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Require all init scripts found:
|
||||||
|
requireAll("init.php");
|
||||||
|
?>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<?php requireAll("head.php"); ?>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="app-header">
|
||||||
|
<?php requireAll("header.php"); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="app-body">
|
||||||
|
<?php
|
||||||
|
try
|
||||||
|
{
|
||||||
|
require Request::getScript();
|
||||||
|
}
|
||||||
|
catch (Exception $x)
|
||||||
|
{
|
||||||
|
ob_clean();
|
||||||
|
header("Content-Type: text/plain");
|
||||||
|
|
||||||
|
$strMessage = $x->getMessage();
|
||||||
|
echo $strMessage;
|
||||||
|
echo "\n\n";
|
||||||
|
|
||||||
|
$strFile = $x->getFile();
|
||||||
|
$intLine = $x->getLine();
|
||||||
|
|
||||||
|
echo "#-1 {$strFile}({$intLine}): {$strMessage}\n";
|
||||||
|
echo $x->getTraceAsString();
|
||||||
|
|
||||||
|
ob_end_flush();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="app-footer">
|
||||||
|
<?php requireAll("footer.php"); ?>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
ob_end_flush();
|
||||||
|
exit;
|
||||||
|
?>
|
43
lib/Cookie.php
Normal file
43
lib/Cookie.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
class Cookie
|
||||||
|
{
|
||||||
|
public static function get($strKey)
|
||||||
|
{
|
||||||
|
if (isset($_COOKIE))
|
||||||
|
if (is_array($_COOKIE))
|
||||||
|
if (array_key_exists($strKey, $_COOKIE))
|
||||||
|
if ($_COOKIE[$strKey] !== null && strlen($_COOKIE[$strKey]) > 0)
|
||||||
|
return $_COOKIE[$strKey];
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function set($strKey, $strValue = null)
|
||||||
|
{
|
||||||
|
if ($strValue == null || strlen($strValue) < 1)
|
||||||
|
{
|
||||||
|
if (isset($_COOKIE[$strKey]))
|
||||||
|
{
|
||||||
|
unset($_COOKIE[$strKey]);
|
||||||
|
setcookie(
|
||||||
|
$strKey,
|
||||||
|
"",
|
||||||
|
time() - 3600,
|
||||||
|
"/");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_COOKIE[$strKey] = $strValue;
|
||||||
|
|
||||||
|
setcookie(
|
||||||
|
$strKey,
|
||||||
|
$strValue,
|
||||||
|
time() + 60 * 60 * 24 * 30,
|
||||||
|
"/");
|
||||||
|
|
||||||
|
return $strValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
150
lib/DatabaseConnection.php
Normal file
150
lib/DatabaseConnection.php
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<?php
|
||||||
|
class DatabaseConnection
|
||||||
|
{
|
||||||
|
private $strEngine;
|
||||||
|
private $strHost;
|
||||||
|
private $strDatabaseName;
|
||||||
|
private $strUsername;
|
||||||
|
private $strPassword;
|
||||||
|
private $pdo;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
$strEngine = null,
|
||||||
|
$strHost = null,
|
||||||
|
$strDatabaseName = null,
|
||||||
|
$strUsername = null,
|
||||||
|
$strPassword = null)
|
||||||
|
{
|
||||||
|
$this->strEngine = $strEngine;
|
||||||
|
|
||||||
|
switch ($strEngine)
|
||||||
|
{
|
||||||
|
case "sqlsrv":
|
||||||
|
$this->pdo = new PDO(
|
||||||
|
"sqlsrv:Server={$strHost};Database={$strDatabaseName}",
|
||||||
|
$strUsername,
|
||||||
|
$strPassword);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "mysql":
|
||||||
|
$this->pdo = new PDO(
|
||||||
|
"mysql:host={$strHost};dbname={$strDatabaseName}",
|
||||||
|
$strUsername,
|
||||||
|
$strPassword);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "sqlite":
|
||||||
|
$strFileName = $strHost;
|
||||||
|
if ($strFileName == null || strlen($strFileName) < 1)
|
||||||
|
$strFileName = ":memory:";
|
||||||
|
|
||||||
|
$this->pdo = new PDO("sqlite:{$strFileName}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Exception("Unknown database engine {$strEngine}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pdo->setAttribute(
|
||||||
|
PDO::ATTR_ERRMODE,
|
||||||
|
PDO::ERRMODE_EXCEPTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function query($input)
|
||||||
|
{
|
||||||
|
$varArgs = self::flatten(func_get_args());
|
||||||
|
|
||||||
|
if (count($varArgs) < 1)
|
||||||
|
throw new Exception("query takes at least one argument, the query!");
|
||||||
|
|
||||||
|
$strQuery = array_shift($varArgs);
|
||||||
|
$strQuery = file_exists("db/{$strQuery}")?
|
||||||
|
file_get_contents("db/{$strQuery}") :
|
||||||
|
$strQuery;
|
||||||
|
|
||||||
|
if ($this->strEngine == "sqlsrv")
|
||||||
|
$strQuery = "set nocount on; {$strQuery}";
|
||||||
|
|
||||||
|
$varStatement = $this->pdo->prepare($strQuery);
|
||||||
|
|
||||||
|
if (count($varArgs) > 0)
|
||||||
|
$varStatement->execute($varArgs);
|
||||||
|
else $varStatement->execute();
|
||||||
|
|
||||||
|
$varTemp = array();
|
||||||
|
$varOutput = array();
|
||||||
|
|
||||||
|
// Engines that do not support multiple rowsets:
|
||||||
|
if (in_array($this->strEngine, array("sqlite")))
|
||||||
|
{
|
||||||
|
while ($varRow = $varStatement->fetch())
|
||||||
|
{
|
||||||
|
$varNewRow = array();
|
||||||
|
|
||||||
|
foreach ($varRow as $k => $v)
|
||||||
|
if (!is_numeric($k))
|
||||||
|
$varNewRow[$k] = $v;
|
||||||
|
|
||||||
|
$varOutput[] = $varNewRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $varOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
try { $varTemp[] = $varStatement->fetchAll(); }
|
||||||
|
catch (Exception $x) {}
|
||||||
|
}
|
||||||
|
while ($varStatement->nextRowset());
|
||||||
|
|
||||||
|
foreach ($varTemp as $i => $varSet)
|
||||||
|
foreach ($varSet as $j => $varRow)
|
||||||
|
{
|
||||||
|
$varNewRow = array();
|
||||||
|
|
||||||
|
foreach ($varRow as $k => $v)
|
||||||
|
if (!is_numeric($k))
|
||||||
|
$varNewRow[$k] = $v;
|
||||||
|
|
||||||
|
$varOutput[] = $varNewRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $varOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
// Used to combine a mixture of values and arrays of values as one flat array of values in the order they arrive in. For example: self::flatten("a", ["b", "c", ["d", "e", "f"], "g"], "h") should return an array of ["a", "b", "c", "d", "e", "f", "g", "h"].
|
||||||
|
private static function flatten(...$args)
|
||||||
|
{
|
||||||
|
if (is_null($args))
|
||||||
|
return array();
|
||||||
|
|
||||||
|
if (count($args) > 0)
|
||||||
|
{
|
||||||
|
$varOutput = array();
|
||||||
|
$varFirst = array_shift($args);
|
||||||
|
|
||||||
|
if (is_array($varFirst))
|
||||||
|
foreach ($varFirst as $varItem)
|
||||||
|
$varOutput = array_merge($varOutput, self::flatten($varItem));
|
||||||
|
else
|
||||||
|
array_push($varOutput, $varFirst);
|
||||||
|
|
||||||
|
if (count($args) > 0)
|
||||||
|
$varOutput = array_merge($varOutput, self::flatten($args));
|
||||||
|
|
||||||
|
return $varOutput;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
142
lib/Request.php
Normal file
142
lib/Request.php
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
<?php
|
||||||
|
class Request
|
||||||
|
{
|
||||||
|
public static $strScriptPath;
|
||||||
|
public static $strResourcePath;
|
||||||
|
public static $varArgs;
|
||||||
|
|
||||||
|
public static function process()
|
||||||
|
{
|
||||||
|
// .htaccess method:
|
||||||
|
$strParam = Request::getParam("path");
|
||||||
|
|
||||||
|
// PHP 5.3.9:
|
||||||
|
if ($strParam == null)
|
||||||
|
$strParam = "";
|
||||||
|
|
||||||
|
// REQUEST_URI method:
|
||||||
|
if (strlen($strParam) < 1)
|
||||||
|
{
|
||||||
|
if (is_array($_SERVER))
|
||||||
|
if (array_key_exists("PATH_INFO", $_SERVER))
|
||||||
|
if (strlen($_SERVER["PATH_INFO"]) > 0)
|
||||||
|
$strParam = $_SERVER["PATH_INFO"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$strPath = $strParam;
|
||||||
|
$strPath = preg_replace("/^\//", "", $strPath);
|
||||||
|
$strPath = preg_replace("/\/$/", "", $strPath);
|
||||||
|
|
||||||
|
// /test/action/a/b/c
|
||||||
|
|
||||||
|
$fncIsFile = function($strScriptPath)
|
||||||
|
{
|
||||||
|
$varSearchPaths = [];
|
||||||
|
|
||||||
|
if (is_dir("plugins"))
|
||||||
|
foreach (scandir("plugins") as $strPluginName)
|
||||||
|
{
|
||||||
|
if ($strPluginName == ".") continue;
|
||||||
|
if ($strPluginName == "..") continue;
|
||||||
|
|
||||||
|
$strNewPath = "plugins/{$strPluginName}";
|
||||||
|
if (is_dir($strNewPath))
|
||||||
|
$varSearchPaths[] = "{$strNewPath}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try the framework's directory last:
|
||||||
|
$varSearchPaths[] = ".";
|
||||||
|
|
||||||
|
foreach ($varSearchPaths as $strSearchPath)
|
||||||
|
if (is_file(Request::$strScriptPath = "{$strSearchPath}/{$strScriptPath}"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Request::$strScriptPath = null;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (strlen($strPath) < 1)
|
||||||
|
if ($fncIsFile("pages/index.php")) break;
|
||||||
|
|
||||||
|
if ($fncIsFile("pages/{$strPath}/index.php")) break;
|
||||||
|
if ($fncIsFile("pages/{$strPath}.php")) break;
|
||||||
|
|
||||||
|
if (preg_match("/\//", $strPath))
|
||||||
|
$strPath = preg_replace("/\/[^\/]{1,}$/", "", $strPath);
|
||||||
|
else $strPath = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
$strArgs = str_replace("/{$strPath}", "", $strParam);
|
||||||
|
$varArgs = explode("/", $strArgs);
|
||||||
|
|
||||||
|
array_shift($varArgs);
|
||||||
|
|
||||||
|
Request::$varArgs = $varArgs;
|
||||||
|
Request::$strResourcePath = $strPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getScript()
|
||||||
|
{
|
||||||
|
return Request::$strScriptPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getArgs()
|
||||||
|
{
|
||||||
|
return Request::$varArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safely returns a request argument by its index or null if it doesn't exist (without error)
|
||||||
|
public static function getArg($intIndex)
|
||||||
|
{
|
||||||
|
if (count(Request::$varArgs) >= $intIndex + 1)
|
||||||
|
return Request::$varArgs[$intIndex];
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safely returns the value of a request parameter or null if not defined (without error)
|
||||||
|
// e.g. 12345 from getParam("id") when resource is like /users/get?id=12345
|
||||||
|
public static function getParam($strKey)
|
||||||
|
{
|
||||||
|
if (is_array($_GET))
|
||||||
|
if (array_key_exists($strKey, $_GET))
|
||||||
|
if (strlen($_GET[$strKey]) > 0)
|
||||||
|
return $_GET[$strKey];
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if all of the arguments are in the POST request
|
||||||
|
// To be used like:
|
||||||
|
// if (Request::posts("param1", "param2")) {
|
||||||
|
public static function posts()
|
||||||
|
{
|
||||||
|
if (is_array($_POST))
|
||||||
|
if (func_num_args() > 0)
|
||||||
|
{
|
||||||
|
foreach (func_get_args() as $strKey)
|
||||||
|
if (!array_key_exists($strKey, $_POST))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the value of the posted field
|
||||||
|
public static function getPosted($strKey)
|
||||||
|
{
|
||||||
|
if (is_array($_POST))
|
||||||
|
if (array_key_exists($strKey, $_POST))
|
||||||
|
if (strlen($_POST[$strKey]) > 0)
|
||||||
|
return $_POST[$strKey];
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call this no matter what to understand the request:
|
||||||
|
Request::process();
|
||||||
|
?>
|
39
lib/Respond.php
Normal file
39
lib/Respond.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
class Respond
|
||||||
|
{
|
||||||
|
public static function status($intCode, $strReason)
|
||||||
|
{
|
||||||
|
$intCode = intval($intCode);
|
||||||
|
$strReason = trim($strReason);
|
||||||
|
|
||||||
|
ob_clean();
|
||||||
|
header("HTTP/1.0 {$intCode} {$strReason}");
|
||||||
|
header("Content-Type: text/plain");
|
||||||
|
|
||||||
|
echo "{$intCode} {$strReason}";
|
||||||
|
|
||||||
|
ob_end_flush();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function json($varInput)
|
||||||
|
{
|
||||||
|
ob_clean();
|
||||||
|
header("Content-Type: application/json");
|
||||||
|
|
||||||
|
echo trim(json_encode($varInput, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_IGNORE));
|
||||||
|
|
||||||
|
ob_end_flush();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function redirect($strLocation)
|
||||||
|
{
|
||||||
|
ob_clean();
|
||||||
|
header("Location: {$strLocation}");
|
||||||
|
|
||||||
|
ob_end_flush();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
4
pages/index.php
Normal file
4
pages/index.php
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?php
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status
|
||||||
|
Respond::status(204, "No Content");
|
||||||
|
?>
|
3
php-webapp-framework.sh
Normal file
3
php-webapp-framework.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
php -S 0.0.0.0:8080 index.php
|
Loading…
Reference in New Issue
Block a user