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();
+
+
+