diff --git a/code/app.php b/code/app.php new file mode 100644 index 00000000..d6b114dc --- /dev/null +++ b/code/app.php @@ -0,0 +1,11 @@ +run(); +} +catch(Exception $e) { + App\Response::generateBadRequestResponse($e->getMessage()); +} diff --git a/code/app_src/Adapter/KitchenAdapter.php b/code/app_src/Adapter/KitchenAdapter.php new file mode 100644 index 00000000..e82d7a86 --- /dev/null +++ b/code/app_src/Adapter/KitchenAdapter.php @@ -0,0 +1,34 @@ +kitchenService = $kitchenService; + } + + public function cookMeal(MealInterface $meal): void + { + $currentIngredients = $meal->getIngredients(); + $meal->resetIngredients(); + $meal->addIngredients($this->kitchenService->fry($currentIngredients)); + + if ($this->kitchenService->checkMealQuality($meal)) { + $meal->setStatus('COOKED_AND_CHECKED'); + } else { + throw new \Exception('Cooking failed'); + } + } + + public function utilizeMeal(MealInterface $meal): void + { + $this->kitchenService->utilize($meal); + } +} \ No newline at end of file diff --git a/code/app_src/Adapter/KitchenService.php b/code/app_src/Adapter/KitchenService.php new file mode 100644 index 00000000..00d09719 --- /dev/null +++ b/code/app_src/Adapter/KitchenService.php @@ -0,0 +1,45 @@ + $value) { + if (in_array($ingredient, self::$needToBeFried)) { + $cookedIngredients["fried_$ingredient"] = $value; + } else { + $cookedIngredients[$ingredient] = $value; + } + } + + return $cookedIngredients; + } + + public function checkMealQuality(MealInterface $meal): bool + { + foreach ($meal->getIngredients() as $ingredient => $value) { + if (in_array($ingredient, self::$needToBeFried)) { + return false; + } + } + + return true; + } + + public function utilize(MealInterface $meal): void + { + $meal->setStatus('UTILIZED'); + } +} \ No newline at end of file diff --git a/code/app_src/Application.php b/code/app_src/Application.php new file mode 100644 index 00000000..7eabd7d5 --- /dev/null +++ b/code/app_src/Application.php @@ -0,0 +1,43 @@ +request = RequestValidator::validate($_POST); + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } + } + + public function run(): void + { + $customer = new Observer\Customer(); + $order = new Strategy\OrderContext($customer); + + switch ($_POST['meal']) { + case 'Burger': + $order->setCookingStrategy(new Strategy\BurgerStrategy()); + break; + + case 'Hotdog': + $order->setCookingStrategy(new Strategy\HotdogStrategy()); + break; + + case 'Sandwich': + $order->setCookingStrategy(new Strategy\SandwichStrategy()); + break; + + default: + throw new Exception('No meal choosed'); + } + + $meal = $order->getOrderedMeal(isset($_POST['client_ingredients']) ? $_POST['client_ingredients'] : []); + print_r($meal->getIngredients()); + } +} diff --git a/code/app_src/Decorator/BurgerDecorator.php b/code/app_src/Decorator/BurgerDecorator.php new file mode 100644 index 00000000..eaf8ce15 --- /dev/null +++ b/code/app_src/Decorator/BurgerDecorator.php @@ -0,0 +1,31 @@ + 3, + 'onion' => 2, + 'lettuce' => 1, + 'cotlete' => 1, + ]; + + public function __construct(MealDecorator $mealDecorator) + { + $this->mealDecorator = $mealDecorator; + } + + function addBaseIngredients(): void + { + $this->mealDecorator->ingredients = array_merge( + $this->mealDecorator->ingredients, + $this->mealDecorator->getAdapter()->createIngredientsArray(self::$baseRecipe) + ); + $this->mealDecorator->setStatus('ADDED_BASE_BURGER_RECIPE_INGREDIENTS'); + } +} \ No newline at end of file diff --git a/code/app_src/Decorator/CustomerIngredientsDecorator.php b/code/app_src/Decorator/CustomerIngredientsDecorator.php new file mode 100644 index 00000000..c1192ac6 --- /dev/null +++ b/code/app_src/Decorator/CustomerIngredientsDecorator.php @@ -0,0 +1,24 @@ +mealDecorator = $mealDecorator; + } + + public function addCustomerIngredients(array $customerIngredients): void + { + $this->mealDecorator->ingredients = array_merge( + $this->mealDecorator->ingredients, + $this->mealDecorator->getAdapter()->createIngredientsArray($customerIngredients) + ); + $this->mealDecorator->setStatus('ADDED_CLIENT_INGREDIENTS'); + } +} \ No newline at end of file diff --git a/code/app_src/Decorator/HotdogDecorator.php b/code/app_src/Decorator/HotdogDecorator.php new file mode 100644 index 00000000..b6c75216 --- /dev/null +++ b/code/app_src/Decorator/HotdogDecorator.php @@ -0,0 +1,30 @@ + 1, + 'mustard' => 1, + 'sausage' => 1, + ]; + + public function __construct(MealDecorator $mealDecorator) + { + $this->mealDecorator = $mealDecorator; + } + + function addBaseIngredients(): void + { + $this->mealDecorator->ingredients = array_merge( + $this->mealDecorator->ingredients, + $this->mealDecorator->getAdapter()->createIngredientsArray(self::$baseRecipe) + ); + $this->mealDecorator->setStatus('ADDED_BASE_HOTDOG_RECIPE_INGREDIENTS'); + } +} \ No newline at end of file diff --git a/code/app_src/Decorator/MealDecorator.php b/code/app_src/Decorator/MealDecorator.php new file mode 100644 index 00000000..b474893c --- /dev/null +++ b/code/app_src/Decorator/MealDecorator.php @@ -0,0 +1,52 @@ +meal = $baseMeal; + $this->adapter = new IngredientAdapter(); + $this->resetIngredients(); + } + + public function resetIngredients(): void + { + $this->ingredients = $this->meal->getIngredients(); + } + + public function setStatus(string $status): void + { + $this->meal->setStatus($status); + } + + public function getBaseMealType(): string + { + return get_class($this->meal); + } + + public function getIngredients(): array + { + return $this->ingredients; + } + + public function addIngredients(array $ingredients = []): void + { + $this->ingredients = array_merge($this->ingredients, $ingredients); + } + + public function getAdapter(): IngredientAdapter + { + return $this->adapter; + } + +} \ No newline at end of file diff --git a/code/app_src/Decorator/SandwichDecorator.php b/code/app_src/Decorator/SandwichDecorator.php new file mode 100644 index 00000000..1f8f516a --- /dev/null +++ b/code/app_src/Decorator/SandwichDecorator.php @@ -0,0 +1,29 @@ + 1, + 'cheese' => 2, + ]; + + public function __construct(MealDecorator $mealDecorator) + { + $this->mealDecorator = $mealDecorator; + } + + function addBaseIngredients(): void + { + $this->mealDecorator->ingredients = array_merge( + $this->mealDecorator->ingredients, + $this->mealDecorator->getAdapter()->createIngredientsArray(self::$baseRecipe) + ); + $this->mealDecorator->setStatus('ADDED_BASE_SANDWICH_RECIPE_INGREDIENTS'); + } +} \ No newline at end of file diff --git a/code/app_src/Factory/BurgerFactory.php b/code/app_src/Factory/BurgerFactory.php new file mode 100644 index 00000000..bdb8a79c --- /dev/null +++ b/code/app_src/Factory/BurgerFactory.php @@ -0,0 +1,14 @@ +setIngredients([ + new Ingredient('bun', 2), + ]); + } +} diff --git a/code/app_src/Meal/Hotdog.php b/code/app_src/Meal/Hotdog.php new file mode 100644 index 00000000..c3f8e213 --- /dev/null +++ b/code/app_src/Meal/Hotdog.php @@ -0,0 +1,14 @@ +setIngredients([ + new Ingredient('bun', 1), + ]); + } +} diff --git a/code/app_src/Meal/Ingredient.php b/code/app_src/Meal/Ingredient.php new file mode 100644 index 00000000..a9f338d3 --- /dev/null +++ b/code/app_src/Meal/Ingredient.php @@ -0,0 +1,26 @@ +name = $name; + $this->amount = $amount; + } + + public function getName(): string + { + return $this->name; + } + + public function getAmount(): int + { + return $this->$amount; + } +} \ No newline at end of file diff --git a/code/app_src/Meal/IngredientAdapter.php b/code/app_src/Meal/IngredientAdapter.php new file mode 100644 index 00000000..717a03e8 --- /dev/null +++ b/code/app_src/Meal/IngredientAdapter.php @@ -0,0 +1,16 @@ + $amount) { + $ingredients[] = new Ingredient($ingredient, (int)$amount); + } + + return $ingredients; + } +} \ No newline at end of file diff --git a/code/app_src/Meal/MealBaseClass.php b/code/app_src/Meal/MealBaseClass.php new file mode 100644 index 00000000..05081540 --- /dev/null +++ b/code/app_src/Meal/MealBaseClass.php @@ -0,0 +1,63 @@ +observers = new SplObjectStorage(); + } + + public function attach(SplObserver $observer): void + { + $this->observers->attach($observer); + } + + public function detach(SplObserver $observer): void + { + $this->observers->detach($observer); + } + + public function setStatus(string $status): void + { + $this->status = $status; + $this->notify(); + } + + public function getStatus(): string + { + return $this->status; + } + + public function notify(): void + { + foreach ($this->observers as $observer) { + $observer->update($this); + } + } + + public function getIngredients(): array + { + return $this->ingredients; + } + + public function setIngredients(array $ingredients = []): void + { + $this->ingredients = $ingredients; + } + + public function addIngredients(array $ingredients = []): void + { + $this->ingredients = array_merge($this->ingredients, $ingredients); + } +} diff --git a/code/app_src/Meal/MealInterface.php b/code/app_src/Meal/MealInterface.php new file mode 100644 index 00000000..12d57c84 --- /dev/null +++ b/code/app_src/Meal/MealInterface.php @@ -0,0 +1,8 @@ +setIngredients([ + new Ingredient('toast', 2), + ]); + } +} diff --git a/code/app_src/Observer/Customer.php b/code/app_src/Observer/Customer.php new file mode 100644 index 00000000..3802d12b --- /dev/null +++ b/code/app_src/Observer/Customer.php @@ -0,0 +1,14 @@ +getStatus() . PHP_EOL); + } +} \ No newline at end of file diff --git a/code/app_src/RequestValidator.php b/code/app_src/RequestValidator.php new file mode 100644 index 00000000..beb3a854 --- /dev/null +++ b/code/app_src/RequestValidator.php @@ -0,0 +1,28 @@ +generateBaseBurger(); + $baseBurger->attach($customer); + $baseBurger->setStatus('STARTED'); + return $this->addIngredients($baseBurger, $customerIngredients); + } + + private function generateBaseBurger(): MealInterface + { + $factory = new BurgerFactory(); + return $factory->createMealBase(); + } + + private function addIngredients(MealInterface $baseBurger, array $customerIngredients): MealInterface + { + $decorator = new Decorator\MealDecorator($baseBurger); + $burgerDecorator = new Decorator\BurgerDecorator($decorator); + $burgerDecorator->addBaseIngredients(); + $customerDecorator = new Decorator\CustomerIngredientsDecorator($decorator); + $customerDecorator->addCustomerIngredients($customerIngredients); + + return $decorator; + } +} diff --git a/code/app_src/Strategy/CookingStrategyInterface.php b/code/app_src/Strategy/CookingStrategyInterface.php new file mode 100644 index 00000000..c0117ea8 --- /dev/null +++ b/code/app_src/Strategy/CookingStrategyInterface.php @@ -0,0 +1,12 @@ +generateBaseHotdog(); + $baseHotdog->attach($customer); + $baseHotdog->setStatus('STARTED'); + return $this->addIngredients($baseHotdog, $customerIngredients); + } + + private function generateBaseHotdog(): MealInterface + { + $factory = new HotdogFactory(); + return $factory->createMealBase(); + } + + private function addIngredients(MealInterface $baseHotdog, array $customerIngredients): MealInterface + { + $decorator = new Decorator\MealDecorator($baseHotdog); + $HotdogDecorator = new Decorator\HotdogDecorator($decorator); + $HotdogDecorator->addBaseIngredients(); + $customerDecorator = new Decorator\CustomerIngredientsDecorator($decorator); + $customerDecorator->addCustomerIngredients($customerIngredients); + + return $decorator; + } +} diff --git a/code/app_src/Strategy/OrderContext.php b/code/app_src/Strategy/OrderContext.php new file mode 100644 index 00000000..e2a6ff88 --- /dev/null +++ b/code/app_src/Strategy/OrderContext.php @@ -0,0 +1,41 @@ +customerObserver = $customer; + } + + public function setCookingStrategy(CookingStrategyInterface $strategy): void + { + $this->strategy = $strategy; + } + + public function getOrderedMeal(array $customerIngredients = []): MealInterface + { + $orderedMeal = $this->strategy->prepareIngredients($customerIngredients, $this->customerObserver); + $orderedMeal->setStatus('INGREDIENTS_PREPARED'); + + try { + $kitchen = new KitchenAdapter(new KitchenService()); + $kitchen->cookMeal($orderedMeal); + } catch (\Exception $e) { + if ($kitchen instanceof KitchenAdapter) { + $kitchen->utilizeMeal($orderedMeal); + } + } + + return $orderedMeal; + } +} diff --git a/code/app_src/Strategy/SandwichStrategy.php b/code/app_src/Strategy/SandwichStrategy.php new file mode 100644 index 00000000..05c51085 --- /dev/null +++ b/code/app_src/Strategy/SandwichStrategy.php @@ -0,0 +1,36 @@ +generateBaseSandwich(); + $baseSandwich->attach($customer); + $baseSandwich->setStatus('STARTED'); + return $this->addIngredients($baseSandwich, $customerIngredients); + } + + private function generateBaseSandwich(): MealInterface + { + $factory = new SandwichFactory(); + return $factory->createMealBase(); + } + + private function addIngredients(MealInterface $baseSandwich, array $customerIngredients): MealInterface + { + $decorator = new Decorator\MealDecorator($baseSandwich); + $SandwichDecorator = new Decorator\SandwichDecorator($decorator); + $SandwichDecorator->addBaseIngredients(); + $customerDecorator = new Decorator\CustomerIngredientsDecorator($decorator); + $customerDecorator->addCustomerIngredients($customerIngredients); + + return $decorator; + } +} diff --git a/code/composer.json b/code/composer.json new file mode 100644 index 00000000..ebb5c094 --- /dev/null +++ b/code/composer.json @@ -0,0 +1,12 @@ +{ + "require": { + "php": ">=7.4", + "ext-json": "*", + "ext-sockets": "*", + }, + "autoload": { + "psr-4": { + "App\\": "app_src" + } + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..4a55c36f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,69 @@ +version: '3' + +services: + nginx-balancer: + build: + context: ./nginx-balancer + dockerfile: Dockerfile + container_name: nginx-balancer + image: localapp/nginx-balancer + ports: + - "80:80" + - "443:443" + depends_on: + - nginx-webserver1 + - nginx-webserver2 + networks: + - app-network + + nginx-webserver1: + build: + context: ./nginx-webserver + dockerfile: Dockerfile + container_name: nginx-webserver1 + image: localapp/nginx-webserver + ports: + - "8081:80" + volumes: + - ./code:/var/www/otus.local + networks: + - app-network + + nginx-webserver2: + build: + context: ./nginx-webserver + dockerfile: Dockerfile + container_name: nginx-webserver2 + image: localapp/nginx-webserver + ports: + - "8082:80" + volumes: + - ./code:/var/www/otus.local + networks: + - app-network + + php1: + build: + context: ./php-fpm + dockerfile: Dockerfile + image: localapp/php + container_name: php1 + volumes: + - ./code:/var/www/otus.local + networks: + - app-network + + php2: + build: + context: ./php-fpm + dockerfile: Dockerfile + image: localapp/php + container_name: php2 + volumes: + - ./code:/var/www/otus.local + networks: + - app-network + +networks: + app-network: + driver: bridge diff --git a/nginx-balancer/Dockerfile b/nginx-balancer/Dockerfile new file mode 100644 index 00000000..082a656f --- /dev/null +++ b/nginx-balancer/Dockerfile @@ -0,0 +1,11 @@ +FROM ubuntu:latest + +RUN apt-get update && apt-get install -y nginx + +COPY ./hosts/nginx-balancer.local.conf /etc/nginx/sites-enabled/otus.local.conf + +WORKDIR /var/www/otus.local +VOLUME /var/www/otus.local +EXPOSE 80 + +CMD [ "nginx", "-g", "daemon off;"] diff --git a/nginx-balancer/hosts/nginx-balancer.local.conf b/nginx-balancer/hosts/nginx-balancer.local.conf new file mode 100644 index 00000000..2c733d23 --- /dev/null +++ b/nginx-balancer/hosts/nginx-balancer.local.conf @@ -0,0 +1,19 @@ +upstream nginx-webservers { + server nginx-webserver1; + server nginx-webserver2; +} + +server { + listen 80; + + server_name otus.local; + error_log /var/log/nginx/error.log; + access_log /var/log/nginx/access.log; + + location / { + proxy_pass http://nginx-webservers; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + } +} diff --git a/nginx-webserver/Dockerfile b/nginx-webserver/Dockerfile new file mode 100644 index 00000000..87b379b2 --- /dev/null +++ b/nginx-webserver/Dockerfile @@ -0,0 +1,11 @@ +FROM ubuntu:latest + +RUN apt-get update && apt-get install -y nginx + +COPY ./hosts/otus.local.conf /etc/nginx/sites-enabled/otus.local.conf + +WORKDIR /var/www/otus.local +VOLUME /var/www/otus.local +EXPOSE 80 + +CMD [ "nginx", "-g", "daemon off;"] diff --git a/nginx-webserver/hosts/otus.local.conf b/nginx-webserver/hosts/otus.local.conf new file mode 100644 index 00000000..4446e512 --- /dev/null +++ b/nginx-webserver/hosts/otus.local.conf @@ -0,0 +1,32 @@ +upstream php-fpm { + server php1:9000; + server php2:9000; +} + +server { + listen 80; + index index.php index.html index.htm; + server_name otus.local; + root /var/www/otus.local; + error_log /var/log/nginx/error.log; + access_log /var/log/nginx/access.log; + + location ~* .(jpg|jpeg|gif|css|png|js|html)$ { + access_log off; + expires max; + } + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~* .php$ { + try_files $uri = 404; + fastcgi_split_path_info (.+?\.php)(/.*)$; + fastcgi_pass php-fpm; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + } +} diff --git a/php-fpm/Dockerfile b/php-fpm/Dockerfile new file mode 100644 index 00000000..61cb608a --- /dev/null +++ b/php-fpm/Dockerfile @@ -0,0 +1,30 @@ +FROM php:8.0-fpm + +RUN apt-get update && apt-get install -y \ + curl \ + wget \ + git \ + libfreetype6-dev \ + libjpeg62-turbo-dev \ + libpng-dev \ + libonig-dev \ + libzip-dev \ + libmcrypt-dev \ + libmemcached-dev\ + libmemcached-tools\ + && pecl install memcached \ + && docker-php-ext-enable memcached \ + && pecl install mcrypt-1.0.4 \ + && docker-php-ext-enable mcrypt \ + && docker-php-ext-install -j$(nproc) iconv mbstring mysqli pdo_mysql zip \ + && docker-php-ext-configure gd --with-freetype --with-jpeg \ + && docker-php-ext-install -j$(nproc) gd \ + && curl -sS https://getcomposer.org/installer \ + | php -- --install-dir=/usr/local/bin --filename=composer + +ADD php.ini /usr/local/etc/php/conf.d/40-custom.ini + +WORKDIR /var/www +VOLUME /var/www + +CMD ["php-fpm"] diff --git a/php-fpm/php.ini b/php-fpm/php.ini new file mode 100644 index 00000000..a58749e0 --- /dev/null +++ b/php-fpm/php.ini @@ -0,0 +1,3 @@ +extension=memcached.so +session.save_handler = memcache +session.save_path = "tcp://memcache:11211"