Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 33 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#### path #######################
CONTROLLERS_BASE_PATH=App/Http/Controllers
VIEW_BASE_PATH=resources/Views
APP_LOG_PATH=log/application.log

#### framework ##################
DEFAULT_CONTROLLER_NAME=Default
DEFAULT_CONTROLLER_METHOD=run
DEFAULT_SCRIPT_NAME=index.php
DEFAULT_VIEW_NAME=index

### sender ######################
NOTIFICATOR=Screen

#### postgres ###################
PGSQL_DB_HOST=postgres
PGSQL_DB_PORT=5432
PGSQL_DB_NAME=postgres
PGSQL_DB_USER=postgres
PGSQL_DB_PASSWORD=password
PGSQL_DRIVER=pgsql
PGSQL_PG_DRIVER=pg_connect

#### redis #######################
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_TIMEOUT=0
REDIS_RESERVED=as
REDIS_RETRY_INTERVAL=0
REDIS_READ_TIMEOUT=0.0

#### Project #####################
PROBABILITY_PRODUCT_DEFECT=50%
174 changes: 174 additions & 0 deletions FastFood.postman_collection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
{
"info": {
"_postman_id": "37a40668-f911-498c-a89a-e041408532b2",
"name": "FastFood",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Sandwich",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "product[size]",
"value": "Маленькая",
"type": "text"
},
{
"key": "product[type]",
"value": "Ржаная",
"type": "text"
},
{
"key": "sauces[mayonnaise]",
"value": "Московский провансаль",
"type": "text"
},
{
"key": "sauces[tabasco]",
"value": "Слабый",
"type": "text"
}
]
},
"url": {
"raw": "{{host}}/FastFood/Sandwich",
"host": [
"{{host}}"
],
"path": [
"FastFood",
"Sandwich"
]
}
},
"response": []
},
{
"name": "Burger",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "product[size]",
"value": "Большой",
"type": "text"
},
{
"key": "product[type]",
"value": "Без кунжута",
"type": "text"
},
{
"key": "sauces[mayonnaise]",
"value": "Ряба оливковый",
"type": "text"
},
{
"key": "sauces[tabasco]",
"value": "Классический",
"type": "text"
},
{
"key": "ingredients[steak]",
"value": "Сильная прожарка",
"type": "text"
},
{
"key": "ingredients[onion]",
"value": "Зеленый репчатый",
"type": "text"
},
{
"key": "ingredients[pepper]",
"value": "Болгарский",
"type": "text"
}
]
},
"url": {
"raw": "{{host}}/FastFood/Burger",
"host": [
"{{host}}"
],
"path": [
"FastFood",
"Burger"
]
}
},
"response": []
},
{
"name": "HotDog",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "product[size]",
"value": "Средний",
"type": "text"
},
{
"key": "product[type]",
"value": "Булка с разрезом",
"type": "text"
},
{
"key": "sauces[mayonnaise]",
"value": "Ряба классический",
"type": "text"
},
{
"key": "sauces[tabasco]",
"value": "Острый",
"type": "text"
},
{
"key": "ingredients[sausage]",
"value": "Сливочная",
"type": "text"
},
{
"key": "ingredients[onion]",
"value": "Зеленый. Перья",
"type": "text"
},
{
"key": "ingredients[pepper]",
"value": "Черный",
"type": "text"
}
]
},
"url": {
"raw": "{{host}}/FastFood/HotDog",
"host": [
"{{host}}"
],
"path": [
"FastFood",
"HotDog"
]
}
},
"response": []
}
],
"variable": [
{
"key": "host",
"value": "homework.otus"
}
]
}
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Домашняя работа по Паттернам

### Требования
1. PHP >=8.0
2. Composer
3. Набор серверов (установленные сервера Redis)

### Используются библиотеки
1. "vlucas/phpdotenv": "^5.1",
2. "monolog/monolog": "2.x-dev",
3. "illuminate/container": "9.x-dev"

### Задание
1. Абстрактная фабрика будет отвечать за генерацию базового продукта-прототипа: бургер, сэндвич или хот-дог
2. При готовке каждого типа продукта Декоратор будет добавлять составляющие к базовому продукту либо по рецепту, либо по пожеланию клиента (салат, лук, перец и т.д.)
3. Наблюдатель подписывается на статус приготовления и отправляет оповещения о том, что изменился статус приготовления продукта.
4. Прокси используется для навешивание пре и пост событий на процесс готовки. Например, если бургер не соответствует стандарту, пост событие утилизирует его.
5. Стратегия будет отвечать за то, что нужно приготовить.
6. Все сущности должны по максимуму генерироваться через DI.

### Описание проекта
Реализованные паттерны:
1) Абстрактная фабрика
[app/Services/Factories/ProductFactory](app/Services/Factories/ProductFactory) - абстрактная фабрика.
AbstractProductFactory.php - класс, используемый в качестве интерфейса для фабрики
[BurgerFactory.php](app/Services/Factories/ProductFactory/BurgerFactory.php) - реальная фабрика для Бургеров,
[HotDogFactory.php](app/Services/Factories/ProductFactory/HotDogFactory.php) - реальная фабрика для Хот-догов,
[SandwichFactory.php](app/Services/Factories/ProductFactory/SandwichFactory.php) - реальная фабрика для Сэндвичей

2) Декоратор
[IIngredient.php](app/Services/Factories/ProductFactory/IIngredient.php) - контракт для Ингредиентов
[ISauce.php](app/Services/Factories/ProductFactory/ISauce.php) - контракт для Соусов
В каждой реальной фабрике например BurgerFactory.php есть класс реализованный абстрактный метод `createIngredients()`
На основании слияния массива с рецептом (набор ингредиентов запрашивается в статическом методе `BurgerRecipe::getIngredient()`)
и пожелания заказчика (`customIngredientsList[]`) формируется Декоратор состоящий из нужных ингредиентов.
В дальнейшем используются методы из базового класса [IIngredient.php](app/Services/Products/Ingredients/Ingredient.php),
которые проходят по всей цепочке ингредиентов. Так же работает и механизм для Соусов.

3) Наблюдатель
[Observer](app/Services/Observer)
Определяем подписчика на события [ProductObserver.php](app/Services/Observer/ProductObserver.php),
где реализовываем метод реакции на событие `update(SplSubject $object)`. Вызов этого метода для подписчиков происходит из базового издателя
например [Ingredients.php](app/Services/Products/Ingredients/Ingredient.php), в котором реализован интерфейс SplSubject.
В нем так же для всех дочерних классов формируются события с помощью вызова метода `$this->notify()`
В свою очередь метод `notify()` передается всем дочерним классам конкретных ингредиентов входящих в декоратор.
Список подписчиков хранится в свойстве `protected SplObjectStorage $observerList;` и слушатели подключаются в [ProductOrder.php](app/Services/Orders/ProductOrder.php)
`$this->ingredients->attach($this->observer);`

4) Заместитель
Используя обертку [ProxyProductOrder.php](./app/Services/Orders/ProxyProductOrder.php) создается заказ,
в котором вызываются методы основного класса [ProductOrder.php](./app/Services/Orders/ProductOrder.php)
и осуществляется анализ созданного продукта синтетическим методом определения качества продукта на основе параметра 'PROBABILITY_PRODUCT_DEFECT' из файла .env
определяющего вероятность. Базово установлено 50% - что продукт будет надлежащего качества.

5) Стратегия
[Strategy](./app/Services/Strategy)
С помощью данного паттерна выбирается стратегия приготовления стейков для бургеров разной прожарки. В классе Steak.php определен метод `setStrategy()`.
Вызов этого метода осуществляется из заказа `ProductOrder.php`. В момент когда вызывается метод prepare() для стейка,
то в нем вызывается метод $this->strategy->prepare(); $this->strategy - хранит класс который должен быть вызван при нужной стратегии.
Таких классов три:
[SteakHard.php](./app/Services/Strategy/CookingTechnology/SteakHard.php)
[SteakMiddle.php](./app/Services/Strategy/CookingTechnology/SteakMiddle.php)
[SteakSoft.php](./app/Services/Strategy/CookingTechnology/SteakSoft.php)

[Helpers/Recipes/](app/Services/Helpers/Recipes) - рецепты продуктов
[Orders](app/Services/Orders) - Заказ продукта, в котором формируется продукт из составляющих

Создание заказа осуществляется в классе [App\Services\Orders\ProductOrder](app/Services/Orders/ProductOrder.php),
через заместитель [App\Services\Orders\ProxyProductOrder](app/Services/Orders/ProxyProductOrder.php),
в который передается фабрика продукта на основе абстрактного класса[ App\Services\Factories\ProductFactory\AbstractProductFactory.php](app/Services/Factories/ProductFactory/AbstractProductFactory.php).
В заказе формируется составляющие продукта c помощью public function createProduct(). Готовится заказ prepareProduct(). И выводится результат getProduct()
Для каждого продукта реализован свой метод в контроллере [FastFoodController.php](app/Http/Controllers/FastFoodController.php)
В них происходит связывание реальной фабрики с абстрактным классом и определение метода оповещения о состоянии заказа для использования контейнера DI
Для этого используется метод `bind()` в базовом контроллере [BaseController](app/Http/Controllers/BaseController.php).
и дальше создается модель использую контейнер из пакета 'illuminate/container'
$this->model = $this->container->make(FastFoodModel::class);
32 changes: 32 additions & 0 deletions app/Console/Routes/ConsoleRouter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Console\Routes;


use Routes\PreparedArgs;
use Routes\Router;


class ConsoleRouter
{
static public function run(array $arguments): void
{
self::setUrlFromCliRequest($arguments);
Router::run();
}

static private function setUrlFromCliRequest(array $arguments): void
{
$argument = mb_strtolower(($arguments[1] ?? '') . '/' . ($arguments[2] ?? ''));
echo $argument.PHP_EOL;
PreparedArgs::setUriRequest(self::getRouteByArgument($argument));
}

static private function getRouteByArgument(string $argument): string
{
return match ($argument) {
'command/' => '/Controller/Method',
default => '',
};
}
}
12 changes: 12 additions & 0 deletions app/Console/start.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
use App\Console\Routes\ConsoleRouter;
use App\Http\Response\ResponseCli;

require_once __DIR__ . '/../../bootstrap/init.php';

try {
ConsoleRouter::run($argv);
}
catch(Exception $ex){
(new ResponseCli())->send($ex->getCode(), $ex->getMessage());
}
42 changes: 42 additions & 0 deletions app/Exceptions/ErrorCodes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace App\Exceptions;


use App\Exceptions\Loader\ViewLoaderException;
use App\Exceptions\Orders\InvalidProductOrderException;
use App\Exceptions\Router\InvalidRouteException;


/**
* Содержит коды ошибок проекта
*/
class ErrorCodes
{
/**
* @var array|int[]
*/
private static array $errorCodes = [
InvalidRouteException::class => 700,
InvalidProductOrderException::class => 701,
ViewLoaderException::class => 900,
];

/**
* @param string $codeName
* @return int
*/
public static function getCode(string $codeName = ''): int
{
return self::$errorCodes[$codeName] ?? 0;
}

/**
* @param int $code
* @return string|false
*/
public static function getMessage(int $code): string|false
{
return array_search($code, self::$errorCodes);
}
}
10 changes: 10 additions & 0 deletions app/Exceptions/ILogged.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace App\Exceptions;

/**
* Интерфейс-маркер включения логирования
*/
interface ILogged
{
}
Loading