Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
5951af2
Serve app using 'php codeholic serve' command...
aditya-zanjad Apr 27, 2022
68bfe94
Fix a few lines of code in the file 'codeholic'
aditya-zanjad Apr 27, 2022
4abab41
Improvize & Rearrange the code a little bit in the file 'codeholic'
aditya-zanjad Apr 28, 2022
53f2fa0
A feature to implement arguments '--port' && '--host' for 'php codeho…
aditya-zanjad Apr 28, 2022
a490756
Improve the code by a little bit...
aditya-zanjad Apr 28, 2022
8916e10
Finalized the code...
aditya-zanjad Apr 28, 2022
4651adb
Add new parameter '--max-port' to define the maximum port number upto…
gg-aditya Jun 21, 2022
3f6c38a
Eliminate nested if conditions. Remove 'exit' statements from the cod…
gg-aditya Jul 19, 2022
65b7706
Add try...catch block. Add nicer formatting when displaying thrown ex…
gg-aditya Jul 20, 2022
9090ad3
Make exception messages clearer & easier to understand...
gg-aditya Jul 22, 2022
3841ab4
Add code for validating the values of --port & --max-port parameters...
gg-aditya Jul 25, 2022
481765d
Correct accidentally setting '' to 'null'...
gg-aditya Jul 25, 2022
52bb194
Code clean up... Add validation logic for '--port' && '--max-port'...
gg-aditya Jul 27, 2022
c704ff7
Fix erroneous code...
gg-aditya Jul 27, 2022
c4e3c67
Update comment(s)...
gg-aditya Jul 29, 2022
5ec48ef
Fix typos...
gg-aditya Aug 5, 2022
2222614
Fix: Next port not being allocated when current port is occupied...
gg-aditya Sep 26, 2022
5492deb
Fix: PHP's built-in server not selecting port dynamically when PHP ve…
gg-aditya Sep 26, 2022
3c0db94
Modify exception messages...
gg-aditya Sep 26, 2022
d307ff8
Remove '.vscode' folder...
gg-aditya Sep 26, 2022
366aeb6
Merge branch 'master' of https://github.com/aditya-zanjad/php-mvc-fra…
gg-aditya Sep 26, 2022
58e956c
Change exception message...
gg-aditya Sep 26, 2022
8bd42de
Fix: Exception message not showing original '' value due it getting i…
gg-aditya Sep 26, 2022
b42c787
Fix types... Change exception message(s)... Add comment(s) to explain…
gg-aditya Sep 27, 2022
1b90549
Change/Add exception message(s)... Add iteration limit for the 'for l…
gg-aditya Sep 27, 2022
9649c7d
Fix typo(s)... Add missing code...
gg-aditya Sep 27, 2022
d5e872b
Fix a few more typo(s)... Change variable name(s)... Add missing code…
gg-aditya Sep 28, 2022
12d972d
Edit comment...
gg-aditya Sep 28, 2022
07b8790
Re-write function 'validatePortNumber'... Re-write comments... Re-wri…
gg-aditya Oct 6, 2022
0a713db
Replace 'exit' with 'return'... Code alignment...
gg-aditya Oct 6, 2022
0538484
Add validation for '--host' parameter...
gg-aditya Oct 6, 2022
a085384
Remove redundant code...
gg-aditya Oct 6, 2022
d8d38be
Update comments... Modify code...
gg-aditya Oct 13, 2022
1b74ccd
Reduce comments... Fix port validation... Change condition in 'for' l…
aditya-zanjad Nov 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@ Minimalistic custom framework created for educational purposes.
6. Go to the `public` folder
7. Start php server by running command `php -S 127.0.0.1:8080`
8. Open in browser http://127.0.0.1:8080
9. You can skip `steps 6 & 7` by running the command `php codeholic serve` in the `terminal / cmd`. You must be in the same directory where this `README.md` file is to perform this step

------
## Installation using docker
Make sure you have docker installed. To see how you can install docker on Windows [click here](https://youtu.be/2ezNqqaSjq8). <br>
Make sure `docker` and `docker-compose` commands are available in command line.

1. Clone the project using git
1. Copy `.env.example` into `.env` (Don't need to change anything for local development)
1. Navigate to the project root directory and run `docker-compose up -d`
1. Install dependencies - `docker-compose exec app composer install`
1. Run migrations - `docker-compose exec app php migrations.php`
8. Open in browser http://127.0.0.1:8080
2. Copy `.env.example` into `.env` (Don't need to change anything for local development)
3. Navigate to the project root directory and run `docker-compose up -d`
4. Install dependencies - `docker-compose exec app composer install`
5. Run migrations - `docker-compose exec app php migrations.php`
6. Open in browser http://127.0.0.1:8080

> The project was created along with Youtube Video Series "[Build PHP MVC Framework](https://www.youtube.com/playlist?list=PLLQuc_7jk__Uk_QnJMPndbdKECcTEwTA1)".
> I appreaciate if you share it.
220 changes: 220 additions & 0 deletions codeholic
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
<?php

try {
// If not even a single command was provided
if (!isset($argv[1])) {
throw new Exception('!!! Zero commands: No command was provided !!!');
}

// Re-arrange the command argument(s)
$arguments = restructureArgsArray($argv);

// Compare command(s) & execute their corresponding code
switch ($argv[1]) {
// For command 'php codeholic serve'
case 'serve':
serveTheApp($arguments);
break;

// If an invalid command is provided
default:
throw new Exception("!!! Invalid command : [ $argv[1] ] !!!");
break;
}
} catch (Throwable $error) {
echo getPrettyError($error->getMessage());
exit;
}

/**
* Re-arrange the user provided command line arguments
*
* @param array $argv => Contains command-line arguments provided by the user
*
* @return array $newArgsArray => Contains restructured command-line arguments
*/
function restructureArgsArray(array $args)
{
// Remove first two 'key => value' pairs from the array as they are not really needed
$args = array_slice($args, 2);
$newArgsArray = [];

// Convert each array element to a new 'key => value' pair
// For example, [ '--port=8080' ] to [ '--port' => '8080' ]
foreach ($args as $arg) {
if (strpos($arg, '=')) {
$newArgsArray[strtok($arg, '=')] = strtok('');
continue;
}

$newArgsArray[$arg] = false;
}

return $newArgsArray;
}

/**
* Serve the app using the command 'php codeholic serve'
*
* @param array $argsArray => A restructured array containing command-line arguments
*
* @return null
*/
function serveTheApp(array $argsArray)
{
$pathToMainIndex = 'public/'; // Path to the entry point of our app
$host = '127.0.0.1'; // Default 'host' name to use
$port = 8080; // Default 'port' to use
$maxPort = null;

$exceptionStart = "!!! 'php codeholic serve': ";
$exceptionEnd = " !!!";

// Valid parameters names for the command 'php codeholic serve'
$validArgs = [
'host' => '--host',
'port' => '--port',
'maxPort' => '--max-port'
];

// Throw an exception for an invalid parameter name
foreach ($argsArray as $arg => $value) {
if (!in_array($arg, $validArgs)) {
throw new Exception($exceptionStart . "Invalid argument provided [ $arg ]" . $exceptionEnd);
}
}

$customHostSpecified = array_key_exists($validArgs['host'], $argsArray);
$customPortSpecified = array_key_exists($validArgs['port'], $argsArray);
$customMaxPortSpecified = array_key_exists($validArgs['maxPort'], $argsArray);

if ($customHostSpecified) {
$host = $argsArray[$validArgs['host']];
}

if ($customPortSpecified) {
$port = $argsArray[$validArgs['port']];
}

if ($customMaxPortSpecified) {
$maxPort = $argsArray[$validArgs['maxPort']];
}

// 'host name' value must not be empty
if (empty($host)) {
throw new Exception($exceptionStart . "Invalid argument value [ --host=$host ]" . $exceptionEnd);
}

validatePortNumber($port, $exceptionStart . "Invalid argument value [ --port=$port ]" . $exceptionEnd);

// Validate 'maxPort' only when it is needed
if ($maxPort) {
validatePortNumber($maxPort, $exceptionStart . "Invalid argument value [ --max-port=$maxPort ]" . $exceptionEnd);
}

// Make sure '--max-port' is not lesser than '--port'
if (isset($maxPort) && $maxPort < $port) {
throw new Exception($exceptionStart . "The value '--max-port=$maxPort' must not be less than '--port=$port'" . $exceptionEnd);
}

// Make sure that the value of '--max-port' is not too high, as we don't want the 'for loop' below to run like forever
if ($maxPort && $maxPort > ($maxPortLimit = $port + 100)) {
$maxPortValidationMessage = "The value [ --max-port=$maxPort ] must not be greater than [ '--port=$port' + 100 = $maxPortLimit ]";
throw new Exception($exceptionStart . $maxPortValidationMessage . $exceptionEnd);
}

// For better readability
$dynamicPortSelectable = version_compare(phpversion(), '8.0.0') > -1 && !$customPortSpecified && !$customMaxPortSpecified;

// If PHP version is 8.0.0 or up, dynamically select a port to run PHP's built-in server on
if ($dynamicPortSelectable) {
executeCommand("php -S $host:0 -t $pathToMainIndex");
return;
}

$maxPort = ($maxPort ?? $port + 60) + 1; // Maximum port for running PHP's built-in server on
$minPort = $port; // Preserve original value of '$port' for further use

// Find out the port that is open & try running PHP's built-in server on it
for ($port; $port < $maxPort; $port++) {
if (executeCommand("php -S $host:$port -t $pathToMainIndex")) {
exit;
}
}

$portUnavailableMessage = "No port within the range [ $minPort <==> $maxPort ] is open. Maybe, try a different ports range";
$exceptionMessage = $exceptionStart . $portUnavailableMessage . $exceptionEnd;

// Set new exception message, when 'minPort' & 'maxPort' are the same
if ($minPort === $maxPort) {
$portUnavailableMessage = "The specified port [ $minPort ] is not open. Maybe, try a different port";
$exceptionMessage = $exceptionStart . $portUnavailableMessage . $exceptionEnd;
}

// If no port was open, throw an exception
throw new Exception($exceptionMessage);
}

/**
* Get nicely formatted error message.
*
* @param string $message
*
* @return string
*/
function getPrettyError(string $message)
{
$asterisks = '';
$asteriskLimit = strlen($message);

for ($i = 0; $i <= $asteriskLimit; $i++) {
$asterisks .= '*';
}

return PHP_EOL . $asterisks . PHP_EOL . $message . PHP_EOL . $asterisks . PHP_EOL;
}

/**
* Try executing the given PHP command without stopping the script execution.
*
* If this function returns true, then it means that the given command was successfully
* executed. Otherwise, the given command failed to execute.
*
* @param string $commandText
*
* @return bool
*/
function executeCommand(string $commandText)
{
exec($commandText, $output, $return);
return $return === 0;
}

/**
* Validate that the given input is a valid port number.
*
* @param mixed $input
* @param string $exceptionMessage
*
* @throws Exception
*
* @return true
*/
function validatePortNumber(mixed $input, string $exceptionMessage = '')
{
$portsRange = [
'options' => [
'min_range' => 0,
'max_range' => 65535
]
];

// The given input must be a valid integer between the range '[ 0 - 65535 ]'
$inputIsValidPort = filter_var($input, FILTER_VALIDATE_INT, $portsRange) || $input === 0;

if ($inputIsValidPort) {
return true;
}

throw new Exception($exceptionMessage);
}