diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..2def84fb5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +composer.phar +/code/vendor +/app +/.idea/ +/db*/ +/*.env +/app/public/logs +/rabbitmq \ No newline at end of file diff --git a/README.md b/README.md index ba7ebff27..6505d81ac 100644 --- a/README.md +++ b/README.md @@ -1 +1,47 @@ -# PHP_2022 \ No newline at end of file +# PHP_2022 +### Design Patterns + +--- +##### Описание +- Фабричный метод - (Products) - генерирует заказанный продукт +- Цепочка обязанностей - Операции(Actions) - добавляют ингредиенты согласно рецепту и пожеланиям заказчика +- Стратегия - Рецепты(Recipes) - определяет порядок приготовления +- Наблюдатель - Монитор в зале(HallDisplay) - выводит статус приготовления +- Прокси - (KitchenWithQualityControl) - расширяет класс Kitchen, добавляет функционал проверки качества продукта + + +###### Порядок запуска +Установить необходимые пакеты§ +```bash +composer install +``` +Собрать и запустить докер +```bash +docker compose up -d --build +``` + +Запустить в консоли обработчик запросов +```bash +docker exec -it fast-food sh +``` + +Сделать заказ +```bash +php app.php -p burger +``` +Сделать заказ и убрать ингредиенты +```bash +php app.php -p burger -e onion +``` + +--- +##### Задание +Разрабатываем часть интернет-ресторана. Продаёт он фаст-фуд. + +1. Фабричный метод будет отвечать за генерацию базового продукта-прототипа, из которого будут готовиться: бургер, сэндвич или хот-дог +2. При готовке каждого типа продукта Цепочка обязанностей будет добавлять составляющие к базовому продукту либо по рецепту, либо по пожеланию клиента (салат, лук, перец и т.д.) +3. Наблюдатель подписывается на статус приготовления и отправляет оповещения о том, что изменился статус приготовления продукта. +4. Прокси используется для навешивания пре и пост событий на процесс готовки. Например, если бургер не соответствует стандарту, пост событие утилизирует его. +5. Стратегия будет отвечать за то, что нужно приготовить. + +Все сущности должны по максимуму генерироваться через DI. diff --git a/code/app.php b/code/app.php new file mode 100644 index 000000000..d5e66a8d7 --- /dev/null +++ b/code/app.php @@ -0,0 +1,12 @@ +run(); +} +catch(\Exception $e){ + echo $e->getMessage(); +} \ No newline at end of file diff --git a/code/composer.json b/code/composer.json new file mode 100644 index 000000000..0bb8e869b --- /dev/null +++ b/code/composer.json @@ -0,0 +1,13 @@ +{ + "name": "ppro/hw20", + "autoload": { + "psr-4": { + "Ppro\\Hw20\\": "src/" + } + }, + "config": { + "platform": { + "php": "8.1" + } + } +} diff --git a/code/composer.lock b/code/composer.lock new file mode 100644 index 000000000..8f5987920 --- /dev/null +++ b/code/composer.lock @@ -0,0 +1,21 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "3a0047c5150f0c0d96cfdd8303e54b94", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "platform-overrides": { + "php": "8.1" + }, + "plugin-api-version": "2.3.0" +} diff --git a/code/src/Actions/Add.php b/code/src/Actions/Add.php new file mode 100644 index 000000000..0f4b9c271 --- /dev/null +++ b/code/src/Actions/Add.php @@ -0,0 +1,19 @@ +getProductObject()->setFinishedProduct(array_merge($product->getProductObject()->getFinishedProduct(),$this->ingredient)); + parent::handle($product); + } +} \ No newline at end of file diff --git a/code/src/Actions/CookAction.php b/code/src/Actions/CookAction.php new file mode 100644 index 000000000..b84ff00fc --- /dev/null +++ b/code/src/Actions/CookAction.php @@ -0,0 +1,26 @@ +nextAction = $action; + return $action; + } + + public function handle(ProductInterface $product): void + { + if ($this->nextAction !== null) { + $this->nextAction->handle($product); + } + } +} \ No newline at end of file diff --git a/code/src/Actions/Fry.php b/code/src/Actions/Fry.php new file mode 100644 index 000000000..94d3e3bef --- /dev/null +++ b/code/src/Actions/Fry.php @@ -0,0 +1,18 @@ +ingredient,fn(&$ingredient,$key,$operation):string => $ingredient = $operation.$ingredient,'Fried '); + $product->getProductObject()->setFinishedProduct(array_merge($product->getProductObject()->getFinishedProduct(),$this->ingredient)); + parent::handle($product); + } +} \ No newline at end of file diff --git a/code/src/Actions/Prepare.php b/code/src/Actions/Prepare.php new file mode 100644 index 000000000..7a2c87c19 --- /dev/null +++ b/code/src/Actions/Prepare.php @@ -0,0 +1,26 @@ +checkIngredientsAvailability($this->ingredient); + parent::handle($product); + } + + private function checkIngredientsAvailability(array $ingredients) + { + //... проверка наличия ингредиентов + $available = true; + if(!$available) + throw new KitchenException("Ingredient does not exist"); + } +} \ No newline at end of file diff --git a/code/src/Application/App.php b/code/src/Application/App.php new file mode 100644 index 000000000..e790fc37e --- /dev/null +++ b/code/src/Application/App.php @@ -0,0 +1,63 @@ +reg = Register::instance(); + } + + /** + * @return void + * @throws AppException + */ + public function run() + { + $this->init(); + $this->handleRequest(); + } + + /** + * @return void + */ + private function init() + { + $this->reg->getApplicationHelper()->init(); + } + + /** + * @return void + * @throws AppException + */ + private function handleRequest() + { + $orderCmd = ($this->reg->getRequest())->getOrder(); + if(empty($orderCmd)) + throw new AppException('Command is empty'); + $orderSets = ($this->reg->getRequest())->getSets(); + $recipeClass = Register::instance()->getRecipe($orderCmd); + $productClass = Register::instance()->getProduct($orderCmd); + $recipeSteps = Register::instance()->getRecipeSteps()->getAll()[$orderCmd] ?? []; + + $order = new Order(); + $order->make($recipeClass, $productClass, $recipeSteps, $orderSets); + } + + + + + +} \ No newline at end of file diff --git a/code/src/Application/ApplicationHelper.php b/code/src/Application/ApplicationHelper.php new file mode 100644 index 000000000..1927e5751 --- /dev/null +++ b/code/src/Application/ApplicationHelper.php @@ -0,0 +1,59 @@ +reg = Register::instance(); + } + + public function init() + { + $this->setupOptions(); + + $request = Request::getInstance(); + $this->reg->setRequest($request); + } + + /** Обработка конфигурационных файлов приложения + * @return void + * @throws AppException + */ + private function setupOptions() + { + if (! file_exists($this->config)) { + throw new AppException("Could not find options file"); + } + + $options = parse_ini_file($this->config, true); + $recipe = new Conf($options['recipe']); + + $this->reg->setRecipes($recipe); + $product = new Conf($options['product']); + $this->reg->setProducts($product); + + if (! file_exists($this->recipesConfig)) { + throw new AppException("Could not find recipes file"); + } + + $recipesConfigArray = parse_ini_file($this->recipesConfig, true); + $recipeSteps = new Conf($recipesConfigArray); + $this->reg->setRecipeSteps($recipeSteps); + + } +} diff --git a/code/src/Application/Conf.php b/code/src/Application/Conf.php new file mode 100644 index 000000000..13a7bad88 --- /dev/null +++ b/code/src/Application/Conf.php @@ -0,0 +1,32 @@ +vals = $vals; + } + + public function get(string $key) + { + if (isset($this->vals[$key])) { + return $this->vals[$key]; + } + return null; + } + + public function set(string $key, $val) + { + $this->vals[$key] = $val; + } + + public function getAll(): array + { + return $this->vals; + } +} diff --git a/code/src/Application/Register.php b/code/src/Application/Register.php new file mode 100644 index 000000000..d9c7e872f --- /dev/null +++ b/code/src/Application/Register.php @@ -0,0 +1,109 @@ +applicationHelper)) { + $this->applicationHelper = new ApplicationHelper(); + } + + return $this->applicationHelper; + } + + public static function reset(): void + { + self::$instance = null; + } + + /** + * @param Request $request + */ + public function setRequest(Request $request): void + { + $this->request = $request; + } + + /** + * @return Request + */ + public function getRequest(): Request + { + return $this->request; + } + + /** + * @param Conf $recipe + */ + public function setRecipes(Conf $recipe): void + { + $this->recipe = $recipe; + } + + /** + * @return Conf + */ + public function getRecipes(): Conf + { + return $this->recipe; + } + public function getRecipe(string $name): string + { + return $this->recipe->get($name) ?? ''; + } + + /** + * @param Conf $recipe + */ + public function setProducts(Conf $product): void + { + $this->product = $product; + } + + /** + * @return Conf + */ + public function getProducts(): Conf + { + return $this->product; + } + public function getProduct(string $name): string + { + return $this->product->get($name) ?? ''; + } + + public function setRecipeSteps(Conf $recipeSteps) + { + $this->recipeSteps = $recipeSteps; + } + public function getRecipeSteps(): Conf + { + return $this->recipeSteps; + } + +} \ No newline at end of file diff --git a/code/src/Application/Request.php b/code/src/Application/Request.php new file mode 100644 index 000000000..edac85f4a --- /dev/null +++ b/code/src/Application/Request.php @@ -0,0 +1,56 @@ +init(); + } + + /** + * @return static + */ + public static function getInstance(): self + { + if (is_null(self::$instance)) + self::$instance = new self(); + return self::$instance; + } + + /** + * @return void + */ + private function init() + { + $opts = getopt('p:e:'); + $this->order = $opts['p'] ?? ''; + $this->sets = isset($opts['e']) ? (is_array($opts['e']) ? $opts['e'] : [$opts['e']]) : []; + + } + + public function getOrder(): string + { + return $this->order; + } + + /** + * @return array + */ + public function getSets(): array + { + return $this->sets; + } +} \ No newline at end of file diff --git a/code/src/Config/options.ini b/code/src/Config/options.ini new file mode 100644 index 000000000..3c8924040 --- /dev/null +++ b/code/src/Config/options.ini @@ -0,0 +1,15 @@ +[recipe] +burger=Ppro\Hw20\Recipes\BurgerRecipe +bigburger=Ppro\Hw20\Recipes\BigBurgerRecipe +superburger=Ppro\Hw20\Recipes\SuperBurgerRecipe +hotdog=Ppro\Hw20\Recipes\HotDogRecipe +spicyhotdog=Ppro\Hw20\Recipes\SpicyHotDogRecipe +sandwich=Ppro\Hw20\Recipes\SandwichRecipe + +[product] +burger=Ppro\Hw20\Products\BurgerFactory +bigburger=Ppro\Hw20\Products\BurgerFactory +superburger=Ppro\Hw20\Products\BurgerFactory +hotdog=Ppro\Hw20\Products\HotDogFactory +spicyhotdog=Ppro\Hw20\Products\HotDogFactory +sandwich=Ppro\Hw20\Products\SandwichFactory \ No newline at end of file diff --git a/code/src/Config/recipes.ini b/code/src/Config/recipes.ini new file mode 100644 index 000000000..5cde59c2d --- /dev/null +++ b/code/src/Config/recipes.ini @@ -0,0 +1,21 @@ +[burger] +step1=bun,\Ppro\Hw20\Actions\Add +step2=sauce,\Ppro\Hw20\Actions\Add +step3=salad,\Ppro\Hw20\Actions\Add +step4=onion,\Ppro\Hw20\Actions\Add +step5=cutlet,\Ppro\Hw20\Actions\Fry +step6=salad,\Ppro\Hw20\Actions\Add +step7=bun,\Ppro\Hw20\Actions\Add + +[sandwich] +step1=bread,\Ppro\Hw20\Actions\Add +step2=sauce,\Ppro\Hw20\Actions\Add +step3=salad,\Ppro\Hw20\Actions\Add +step4=ham,\Ppro\Hw20\Actions\Add +step5=bread,\Ppro\Hw20\Actions\Add + +[hotdog] +step1=bun,\Ppro\Hw20\Actions\Add +step2=sauce,\Ppro\Hw20\Actions\Add +step3=salad,\Ppro\Hw20\Actions\Add +step4=sausage,\Ppro\Hw20\Actions\Fry \ No newline at end of file diff --git a/code/src/Entity/DtoInterface.php b/code/src/Entity/DtoInterface.php new file mode 100644 index 000000000..fce67d41f --- /dev/null +++ b/code/src/Entity/DtoInterface.php @@ -0,0 +1,8 @@ +ingredients = $ingredients; + } + + + public function getIngredients(array $ingredients): array + { + return $this->ingredients; + } + +} \ No newline at end of file diff --git a/code/src/Entity/ProductDto.php b/code/src/Entity/ProductDto.php new file mode 100644 index 000000000..ab73a8715 --- /dev/null +++ b/code/src/Entity/ProductDto.php @@ -0,0 +1,57 @@ +finishedProduct; + } + + /** + * @param array $finishedProduct + */ + public function setFinishedProduct(array $finishedProduct): void + { + $this->finishedProduct = $finishedProduct; + } + + /** + * @param string $status + */ + public function setStatus(string $status): void + { + $this->status = $status; + } + + public function getStatus(): string + { + return $this->status; + } + + private function getProductInfo(): array + { + return [ + 'PRODUCT' => $this->finishedProduct, + ]; + } + + public function getProductInfoJson() + { + return json_encode($this->getProductInfo()); + } + + public function utilize() + { + $this->finishedProduct = []; + } + +} \ No newline at end of file diff --git a/code/src/Exceptions/AppException.php b/code/src/Exceptions/AppException.php new file mode 100644 index 000000000..68be1de21 --- /dev/null +++ b/code/src/Exceptions/AppException.php @@ -0,0 +1,12 @@ +subscribers[] = $subscriber; + } + + public function unsubscribe(ProductSubscriberInterface $subscriber) + { + // TODO: Implement unsubscribe() method. + } + + public function notify(ProductInterface $product) + { + foreach ($this->subscribers as $subscriber) { + $subscriber->update($product); + } + } +} \ No newline at end of file diff --git a/code/src/Observers/ProductPublisherInterface.php b/code/src/Observers/ProductPublisherInterface.php new file mode 100644 index 000000000..fe9a81abc --- /dev/null +++ b/code/src/Observers/ProductPublisherInterface.php @@ -0,0 +1,12 @@ +product = new ProductDto(); + } + + /** + * @return ProductDto + */ + public function getProductObject(): ProductDto + { + return $this->product; + } + + /** + * @param CookAction $cookAction + * @return void + */ + public function productCook(CookAction $cookAction): void + { + $cookAction->handle($this); + } + + /** + * @param string $status + * @return void + */ + public function setStatus(string $status): void + { + $this->product->setStatus($status); + $this->notify($this); + } + + /** + * @return void + */ + public function utilizeProduct() + { + $this->product->utilize(); + } +} \ No newline at end of file diff --git a/code/src/Products/ProductFactoryInterface.php b/code/src/Products/ProductFactoryInterface.php new file mode 100644 index 000000000..3979413ca --- /dev/null +++ b/code/src/Products/ProductFactoryInterface.php @@ -0,0 +1,15 @@ +recipeSteps,function(&$step){$step = explode(",",$step);}); + $this->ingredients = new IngredientsDto(); + $this->ingredients->setIngredients($this->getIngredientsArray()); + } + + /** + * @return ProductFactoryInterface + */ + public function getProductFactory(): ProductFactoryInterface + { + return $this->productFactory; + } + + /** + * @return IngredientsDto + */ + public function getIngredients(): IngredientsDto + { + return $this->ingredients; + } + + /** + * @return array + */ + public function getIngredientsArray(): array + { + return array_map(fn($item):string => reset($item),$this->getRecipeByStep()); + } + + /** + * @return array + */ + private function getRecipeByStep() + { + return array_filter($this->recipeSteps,fn($recipeItem)=>!in_array(reset($recipeItem),$this->orderSets)); + } + + /** Формирование цепочки обязанностей (последовательность приготовления) + * @return CookAction + */ + public function getProcess(): CookAction + { + $firstHandler = new Prepare($this->getIngredientsArray()); + $process = $this->getRecipeByStep(); + array_reduce($process,function($currentHandler,$processItem){ + list($ingredient,$action) = $processItem; + return $currentHandler->setNextAction(new $action([$ingredient])); + },$firstHandler); + return $firstHandler; + } +} \ No newline at end of file diff --git a/code/src/Recipes/RecipeStrategyInterface.php b/code/src/Recipes/RecipeStrategyInterface.php new file mode 100644 index 000000000..cc6859d05 --- /dev/null +++ b/code/src/Recipes/RecipeStrategyInterface.php @@ -0,0 +1,10 @@ +getProductObject()->getStatus(); + } +} \ No newline at end of file diff --git a/code/src/Services/Kitchen.php b/code/src/Services/Kitchen.php new file mode 100644 index 000000000..929ad9a8f --- /dev/null +++ b/code/src/Services/Kitchen.php @@ -0,0 +1,43 @@ +product = (new ($this->recipe->getProductFactory()))->create(); + } + + /** Приготовление продукта + * @return void + */ + public function productCook() + { + $this->product->setStatus('The kitchen has started cooking'.PHP_EOL); + $this->product->productCook($this->recipe->getProcess()); + $this->product->setStatus('The kitchen has finished cooking'.PHP_EOL); + } + + /** + * @return ProductInterface + */ + public function getProduct() + { + return $this->product; + } +} \ No newline at end of file diff --git a/code/src/Services/KitchenWithQualityControl.php b/code/src/Services/KitchenWithQualityControl.php new file mode 100644 index 000000000..d92459afd --- /dev/null +++ b/code/src/Services/KitchenWithQualityControl.php @@ -0,0 +1,52 @@ +product->setStatus('The kitchen has started cooking'.PHP_EOL); + $this->product->productCook($this->recipe->getProcess()); + try { + $this->checkProductQuality(); + } catch (QualityException $e){ + //.. remake product or ... + echo $e->getMessage(); + if($this->retryCount++ > 2) + throw new KitchenException('The chef is not at his best today'); + $this->product->utilizeProduct(); + $this->productCook(); + return; + } + $this->product->setStatus('The kitchen has finished cooking'.PHP_EOL); + } + + /** + * @return void + * @throws QualityException + */ + private function checkProductQuality() + { + //... + //проверка качества продукта + $quality = rand(90,100) > 95; + if($quality) + throw new QualityException('Product quality must be up to 95'.PHP_EOL); + } +} \ No newline at end of file diff --git a/code/src/Services/MenuBook.php b/code/src/Services/MenuBook.php new file mode 100644 index 000000000..c78d9e483 --- /dev/null +++ b/code/src/Services/MenuBook.php @@ -0,0 +1,39 @@ +product = new $productClass(); + if(empty($recipeSteps)) + throw new AppException('RecipeSteps not found'); + $this->recipe = new $recipeClass($this->product, $recipeSteps, $this->orderSets); + + } + + public function getRecipe(): RecipeStrategyInterface + { + return $this->recipe; + } + + public function getReadyProduct(): ProductFactoryInterface + { + return $this->product; + } +} \ No newline at end of file diff --git a/code/src/Services/Order.php b/code/src/Services/Order.php new file mode 100644 index 000000000..c1265cc04 --- /dev/null +++ b/code/src/Services/Order.php @@ -0,0 +1,33 @@ +getRecipe()); + $kitchen->getProduct()->subscribe(new HallDisplay()); + $kitchen->productCook(); + echo $kitchen->getProduct()->getProductObject()->getProductInfoJson().PHP_EOL; + } catch (KitchenException|AppException $e) { + echo $e->getMessage(); + } + } +} \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 000000000..aee0498af --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,13 @@ +version: '3' + +services: + #Контейнер с PHP-FPM + app: + build: + context: ./fpm + dockerfile: Dockerfile + image: myapp/php + container_name: fast-food + volumes: + - ./code:/data + diff --git a/fpm/Dockerfile b/fpm/Dockerfile new file mode 100644 index 000000000..9e0f79a23 --- /dev/null +++ b/fpm/Dockerfile @@ -0,0 +1,7 @@ +FROM php:8.1-fpm + +WORKDIR /data + +VOLUME /data + +CMD ["php-fpm"] diff --git a/results/results.png b/results/results.png new file mode 100644 index 000000000..8eec60c49 Binary files /dev/null and b/results/results.png differ