diff --git a/.env b/.env new file mode 100644 index 0000000..e90b489 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +APP_STORAGE=./store.json \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f454177 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +composer.lock +/vendor/ +.idea +store.json \ No newline at end of file diff --git a/App/Commands/BaseCommandController.php b/App/Commands/BaseCommandController.php new file mode 100644 index 0000000..535d950 --- /dev/null +++ b/App/Commands/BaseCommandController.php @@ -0,0 +1,145 @@ + true, + "N" => false + ]; + + public function __construct( + protected InputConsole $input, + protected OutputConsole $output, + protected StoreManager $store, + protected InputValidator $validator, + protected Fast $newFast + ){ + $this->setFastTypes(); + } + + /** + * Sets fast starting date from user input. + * @throws Exception + */ + protected function getStartDate() + { + $this->output->write('Enter Start Date of Fast format:(Y-m-d H:i:s) => (2020-10-10 20:00:00)', 'yellow'); + $userInput = $this->input->getInput(); + + while ($message = $this->validator->validateStartDate($userInput)) { + $this->output->write($message, 'red'); + $userInput = $this->input->getInput(); + } + $this->newFast->set([ + 'start' => $userInput + ]); + } + + /** + * Prints fast types in console and sets fast type from user input. + * @throws Exception + */ + protected function getFastType() + { + $this->output->write('Select a fast type', 'yellow'); + $this->printFastTypes(); + $userInput = $this->input->getInput(); + + while (!isset($this->fastTypes[$userInput])) { + $this->output->write('Please choose from existing types.', 'red'); + $userInput = $this->input->getInput(); + } + $this->newFast->set([ + 'type' => $this->fastTypes[$userInput]['value'] + ]); + } + + /** + * Sets $fastTypes property as array from FastType enum class + */ + protected function setFastTypes() + { + foreach (FastType::getAll() as $const => $value) { + $this->fastTypes[] = [ + 'const' => $const, + 'value' => $value + ]; + } + } + + /** + * Prints Fast types from fastTypes property. + */ + protected function printFastTypes() + { + foreach ($this->fastTypes as $key => $value) { + $this->output->write("[$key] " . $value['const'] . " ({$value['value']}" . 'h)', 'yellow'); + } + } + + /** + * Sets the newly created fast end Date + */ + protected function setFastEndDate() + { + $hours = 'PT' . $this->newFast->type . 'H'; + $fastEndDate = $this->newFast + ->start + ->add(new \DateInterval("$hours")) + ->format('Y-m-d H:i:s'); + + $this->newFast->set([ + 'end' => $fastEndDate + ]); + } + + /** + * Saves the newly created fast into the store file. + */ + protected function saveFast() + { + $storeFasts = $this->store->getAll()->toArray(); + + $storeFasts[] = $this->newFast; + + $this->store->write($storeFasts); + } + + /** + * Saves a single Fast object passed as a parameter in the store file. + * @param Fast $fast + * @throws Exception + */ + protected function save(Fast $fast) + { + $storeFasts = $this->store->getAll()->toArray(); + $storeFasts[] = $fast; + $this->store->write($storeFasts); + } + + protected function askForConfirmation(string $question): string + { + $this->output->write($question, 'yellow'); + $this->printConfirmationMenu(); + return strtoupper($this->input->getInput()); + } + + private function printConfirmationMenu() + { + foreach ($this->confirmationOptions as $key => $value) { + $this->output->write("[$key]", 'yellow'); + } + } +} \ No newline at end of file diff --git a/App/Commands/CheckCommand.php b/App/Commands/CheckCommand.php new file mode 100644 index 0000000..4684a75 --- /dev/null +++ b/App/Commands/CheckCommand.php @@ -0,0 +1,17 @@ +store->getActiveFast()) { + $this->output->write('You have no active fast please create one.', 'red'); + return; + } + $this->output->write($activeFast->print(), 'green'); + } +} \ No newline at end of file diff --git a/App/Commands/CommandController.php b/App/Commands/CommandController.php new file mode 100644 index 0000000..2610968 --- /dev/null +++ b/App/Commands/CommandController.php @@ -0,0 +1,129 @@ + 'Check the fast status', + 'command' => CheckCommand::class, + 'condition' => 'noCondition' + ], + [ + 'option' => 'Create Fast', + 'command' => CreateCommand::class, + 'condition' => 'canCreate' + ], + [ + 'option' => 'Update an active fast', + 'command' => UpdateCommand::class, + 'condition' => 'activeFasts', + + ], + [ + 'option' => 'End an active fast', + 'command' => EndCommand::class, + 'condition' => 'activeFasts', + + ], + [ + 'option' => 'List all fasts', + 'command' => ListCommand::class, + 'condition' => 'noCondition' + ], + [ + 'option' => 'Exit', + 'command' => ExitCommand::class, + 'condition' => 'noCondition' + ], + + ]; + + public function __construct( + protected InputConsole $input, + protected OutputConsole $output, + protected FileManagerInterface $store, + protected InputValidator $validator, + protected Fast $newFast, + ){} + + public function run() + { + while ($this->appRunning) { + $this->updateActiveFastParameter(); + $this->updateCanCreateParameter(); + $this->adjustMenuAndPrint(); + $input = $this->input->getInput(); + if (key_exists($input, $this->menu)) { + /** + * @var $command BaseCommandInterface + */ + $command = new $this->availableCommands[$input]( + $this->input, + $this->output, + $this->store, + $this->validator, + $this->newFast, + ); + $command->run(); + } else { + $this->output->write('Please select a specific command.', 'yellow'); + } + } + } + + public function adjustMenuAndPrint() + { + $counter = 0; + foreach ($this->menu as $key => $bundle) { + ++$counter; + $condition = $bundle['condition']; + if ($this->{$condition}) { + $this->output->write("[" . $counter . "] " . $bundle['option']); + $this->availableCommands[$counter] = $bundle['command']; + } else { + $counter--; + } + } + } + + /** + * @throws \Exception + */ + private function updateActiveFastParameter() + { + if ($this->store->hasActiveFasts()) { + $this->activeFasts = true; + return; + } + $this->activeFasts = false; + } + + /** + * @throws \Exception + */ + private function updateCanCreateParameter() + { + if (!$this->store->hasActiveFasts()) { + $this->canCreate = true; + return; + } + $this->canCreate = false; + } +} \ No newline at end of file diff --git a/App/Commands/CreateCommand.php b/App/Commands/CreateCommand.php new file mode 100644 index 0000000..30ef7d5 --- /dev/null +++ b/App/Commands/CreateCommand.php @@ -0,0 +1,17 @@ +getStartDate(); + $this->getFastType(); + $this->setFastEndDate(); + $this->saveFast(); + } +} \ No newline at end of file diff --git a/App/Commands/EndCommand.php b/App/Commands/EndCommand.php new file mode 100644 index 0000000..08039d6 --- /dev/null +++ b/App/Commands/EndCommand.php @@ -0,0 +1,35 @@ +store->getActiveFast()) { + $userInput = $this->askForConfirmation("Are you sure you want to end current active fast?"); + while (!key_exists($userInput, $this->confirmationOptions)) { + $userInput = $this->input->getInput(); + } + if (!$this->confirmationOptions[$userInput]) return; + // Once we have confirmation delete the current active fast from file. + $this->store->deleteActiveFast(); + $activeFast->set([ + 'status' => Status::INACTIVE, + ]); + if ($activeFast->start < $today) { + $activeFast->set([ + 'elapsedTime' => $today->diff($activeFast->start) + ->format('%Y years %m months %d days %H h %i min %s sec') + ]); + } + //Save the updated fast into file. + $this->save($activeFast); + } + } +} \ No newline at end of file diff --git a/App/Commands/ExitCommand.php b/App/Commands/ExitCommand.php new file mode 100644 index 0000000..4e88de5 --- /dev/null +++ b/App/Commands/ExitCommand.php @@ -0,0 +1,29 @@ +askForConfirmation("Are you sure you want to exit the app?"); + if (!$this->confirmationOptions[$userInput]) return; + $this->output->write('See you soon.. Goodbye....', 'green'); + exit; + } +} \ No newline at end of file diff --git a/App/Commands/ListCommand.php b/App/Commands/ListCommand.php new file mode 100644 index 0000000..d2341d6 --- /dev/null +++ b/App/Commands/ListCommand.php @@ -0,0 +1,43 @@ +store->getAll(); + $this->listFasts($fasts); + } + + /** + * Accepts a collection and prints out in console. + * @param Collection $fasts + */ + private function listFasts(Collection $fasts) + { + $fasts->each(function ($key, $fast) { + if ($fast->status !== Status::ACTIVE) { + $this->output->write("Fast number:$key {$fast->print()}", 'red'); + + }else{ + $this->output->write("Fast number:$key {$fast->print()}", 'green'); + } + }); + } +} \ No newline at end of file diff --git a/App/Commands/UpdateCommand.php b/App/Commands/UpdateCommand.php new file mode 100644 index 0000000..f129563 --- /dev/null +++ b/App/Commands/UpdateCommand.php @@ -0,0 +1,23 @@ +store->getActiveFast()) { + $userInput = $this->askForConfirmation("Are you sure you want to update your current active fast?"); + if (!$this->confirmationOptions[$userInput]) return; + $this->output->write("This is your active fast {$activeFast->print()} Please reset start date and fast type:"); + $this->getStartDate(); + $this->getFastType(); + $this->setFastEndDate(); + $this->store->deleteActiveFast(); + $this->saveFast(); + } + } +} \ No newline at end of file diff --git a/App/Console/InputConsole.php b/App/Console/InputConsole.php new file mode 100644 index 0000000..a245295 --- /dev/null +++ b/App/Console/InputConsole.php @@ -0,0 +1,17 @@ +format("Y-m-d H:i:s") != $input)) { + return "Please enter valid format date."; + } else if ($dateInputObject < $today) { + return 'Please set starting date greater than today.'; + } + return false; + } +} \ No newline at end of file diff --git a/App/Console/OutputConsole.php b/App/Console/OutputConsole.php new file mode 100644 index 0000000..f7abf4b --- /dev/null +++ b/App/Console/OutputConsole.php @@ -0,0 +1,28 @@ + "[1;37m", + 'yellow' => "[33;1m", + 'green' => "[32;1m", + 'red' => "[41;1m" + ]; + + /** + * Echos passed string into console as text + * @param $text + * @param string $color + * color options (white{default} , yellow , green , red{outputs white text with red background}) + */ + public function write($text, string $color = 'white') + { + $outputColor = $this->colors["$color"] ?? "[1;37m"; + echo "\e" . $outputColor . $text . "\e[0m" . "\n\r"; + } +} \ No newline at end of file diff --git a/App/Enums/BasicEnum.php b/App/Enums/BasicEnum.php new file mode 100644 index 0000000..a98aa35 --- /dev/null +++ b/App/Enums/BasicEnum.php @@ -0,0 +1,62 @@ +getConstants(); + } + return self::$constCacheArray[$calledClass]; + } + + /** + * @param mixed $value + * @return bool|int|string + * @throws ReflectionException + */ + public static function fromValue(mixed $value): bool|int|string + { + $constants = self::getConstants(); + + return array_search($value, $constants); + } + + /** + * Checks if the passed value exist as a constant in the called enum class. + * @param string $value + * @return bool + * @throws ReflectionException + */ + public static function isValid(string $value): bool + { + $constants = self::getConstants(); + return array_key_exists($value, $constants); + } + + /** + * Return all constants of the called enum class + * @return array + * @throws ReflectionException + */ + public static function getAll(): array + { + return self::getConstants(); + } +} \ No newline at end of file diff --git a/App/Enums/FastType.php b/App/Enums/FastType.php new file mode 100644 index 0000000..ef7799b --- /dev/null +++ b/App/Enums/FastType.php @@ -0,0 +1,12 @@ +items); + } + + public function next() :void + { + next($this->items); + } + + public function key() : mixed + { + return key($this->items); + } + + public function valid() : bool + { + return current($this->items) !== false; + } + + public function rewind() : void + { + reset($this->items); + } + + /** + * Iterates the collection over a callback function + * @param callable $callback + * @return $this|false + */ + public function each(callable $callback): bool|static + { + foreach ($this as $key => $value) { + if ($callback($key, $value) === false) { + return false; + } + } + return $this; + } + + /** + * Returns collection as an array + * @return array + */ + public function toArray() + { + return $this->items; + } +} \ No newline at end of file diff --git a/App/Model/Fast.php b/App/Model/Fast.php new file mode 100644 index 0000000..9fdb166 --- /dev/null +++ b/App/Model/Fast.php @@ -0,0 +1,97 @@ +dates)) { + return new DateTime($this->{$parameter}); + } + + return $this->$parameter; + } + + /** + * Global params setter for fillable properties of Fast class. + * @param array $params + * @throws Exception + */ + public function set(array $params) + { + foreach ($params as $param => $value) { + if (!in_array($param, $this->fillable)) throw new Exception("Cannot set property {$param} of " . get_class($this)); + $this->$param = $value; + } + } + + /** + * @return string + * @throws \ReflectionException + */ + public function print(): string + { + return " +-------------------------------------- +Status (" . Status::fromValue($this->status) . ") \n\r +Started Fasting $this->start \n\r +End date $this->end \n\r +Fast Type " . FastType::fromValue($this->type) . "({$this->type}h) \n\r +Elapsed Time $this->elapsedTime; \n\r +-------------------------------------- "; + } + + /** + * @return array + */ + #[ArrayShape(["status" => "int", "start" => "string", "end" => "string", "type" => "int", "elapsed_time" => "string"])] + public function jsonSerialize(): array + { + return [ + "status" => $this->status, + "start" => $this->start, + "end" => $this->end, + "type" => $this->type, + "elapsed_time" => $this->elapsedTime + ]; + } +} + + diff --git a/App/Store/StoreManager.php b/App/Store/StoreManager.php new file mode 100644 index 0000000..aa1a473 --- /dev/null +++ b/App/Store/StoreManager.php @@ -0,0 +1,136 @@ +file = $_ENV['APP_STORAGE']; + + } + + /** + * Fetches all fasts from store.json if any and returns a collection + * @return Collection + * @throws Exception + */ + public function getAll(): Collection + { + $today = new DateTime('NOW'); + $fastArray = []; + $storeFasts = Json::decode( + file_get_contents("$this->file") + , false); + if (!$storeFasts) { + return new Collection([]); + } + foreach ($storeFasts as $fast) { + $newFast = new Fast( + start: $fast->start, + status: $fast->status, + end: $fast->end, + type: $fast->type + ); + if ($fast->status === Status::ACTIVE && $newFast->start < $today) { + $newFast->set( + [ + 'elapsedTime' => $today + ->diff($newFast->start) + ->format('%Y years %m months %d days %H h %i min %s sec') + ] + ); + + } else { + $newFast->set([ + 'elapsedTime' => $fast->elapsed_time + ]); + } + $fastArray[] = $newFast; + } + return new Collection($fastArray); + } + + /** + * Checks if there are any active fasts in file. + * @return bool + * @throws Exception + */ + public function hasActiveFasts(): bool + { + $hasActive = false; + $fasts = $this->getAll(); + + if (!$fasts->toArray()) { + return $hasActive; + } + $fasts->each(function ($key, $fast) use (&$hasActive) { + if ($fast->status == Status::ACTIVE) { + $hasActive = true; + } + }); + return $hasActive; + } + + + /** + * @return bool|Fast + * @throws Exception + */ + public function getActiveFast(): bool|Fast + { + if (!$this->hasActiveFasts()) return false; + + $fasts = $this->getAll(); + $activeFast = false; + + $fasts->each(function ($key, $fast) use (&$activeFast) { + if ($fast->status == Status::ACTIVE) { + $activeFast = $fast; + } + }); + return $activeFast; + } + + /** + * Writes the past parameter into the store file. + * @param $fasts + */ + public function write($fasts) + { + file_put_contents($this->file, Json::encode($fasts)); + } + + /** + * Delete an Active fast from store file + */ + public function deleteActiveFast() + { + $fasts = $this->getAll(); + $fastsWithoutActiveFasts = []; + + $fasts->each(function ($key, $fast) use (&$fastsWithoutActiveFasts) { + if ($fast->status == Status::INACTIVE) { + $fastsWithoutActiveFasts[] = $fast; + } + }); + + $this->write($fastsWithoutActiveFasts); + } +} \ No newline at end of file diff --git a/README.md b/README.md index 03bb225..83aa014 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,16 @@ # Zero-consoleApp -PHP console app based on Zero + +Simple console app for tracking fasts written in PHP. + +# Requirements + +1. PHP ^8.0 +2. Composer package see installation -> Composer + +# Instalation + +1. Clone or download from repository. +2. Get into the folder directory cd ./Zero-consoleApp +3. Run composer install +4. Run php fast +5. Enjoy ;) \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c6f49db --- /dev/null +++ b/composer.json @@ -0,0 +1,10 @@ +{ + "autoload": { + "psr-4": { + "App\\": "./App" + } + }, + "require": { + "vlucas/phpdotenv": "^5.4" + } +} diff --git a/fast b/fast new file mode 100644 index 0000000..7cebdbd --- /dev/null +++ b/fast @@ -0,0 +1,25 @@ +#!/usr/bin/env php +load(); + +$commands = new CommandController( + new InputConsole(), + new OutputConsole(), + new StoreManager(), + new InputValidator(), + new Fast() +); +$commands->run(); + + +