#model-view-controller #orm #doctrine-orm #symfony #business-logic
#модель-представление-контроллер #orm #doctrine-orm #symfony #бизнес-логика
Вопрос:
В моем приложении есть несколько сложный механизм ценообразования — вот некоторые из моих бизнес-правил для настройки сцены (объекты выделены жирным шрифтом):
- У продукта могут быть уникальные цены для данного клиента, веб-сайта или группы клиентов.
- У продукта иногда может быть одна или несколько дополнительных опций, которые могут иметь свои собственные ценовые точки или ценовые правила.
- У продукта есть одно уникальное дополнение, выбранное пользователем, которое по сути является ценой и целым числом.
Прямо сейчас у меня есть EntityRepository для ценовых пунктов, чтобы по существу определить правильную цену для базового продукта. То же самое касается уникального добавления и опций.
PricePointRepository
public function getThePrice($Product, $qty, $Website, $Customer = null)
{
//all logic to get product price for this given instance goes here. Good.
}
Контроллер (упрощенный)
public function indexAction()
{
$Product = $em->dostuffwithpostdata;
$qty = POST['qty']; //inb4insecure trolls
$Website = $em->dostuff();
$Customer = (if user is logged in, return their object with $em, otherwise null as it is a guest or public person); // No business logic here, just understanding the request.
$price = $em->getRepository(PricePointRepository)->getThePrice($Product,$qty,Website,$Customer);
$Options[] = $em->dostuffwithPOSTdata;
$optionsPrice = 0;
//Below is some logic directly related to pricing the product.
foreach($Options as $option) {
if($option->hasRule()) {
$optionsPrice = $ruleprice; //after some other stuff of course)
} else {
$optionsPrice = $em->getRepository(OptionPricePoints)->getPrice($option->getID(),$qty);
}
}
$uniqueAdditionPrice = $em->stuff;
$finalprice = $price $optionsPrice $uniqueAdditionPrice; //This is logic related to how I price this type of product!
$unitprice = $finalprice / $qty;
//twig stuff to render and show $finalprice, $unitprice, $uniqueAdditionPrice
}
Это только для страницы продукта. Что происходит, когда я добираюсь до корзины, сохраняю заказ и т.д., Когда эту логику необходимо использовать повторно. Как вы можете видеть, я повсюду использую Doctrine для извлечения данных на основе моей бизнес-логики в классах репозитория.
Я с радостью приветствую неправильные ответы, потому что я действительно думаю, что это неправильно. Как мне это исправить? Чем-то прекрасным был бы сервис, который по сути выглядит следующим образом:
$pricer = getPricerService->Pricer($Entities,$postdata,$etc);
$unitPrice = $pricer->getUnitPrice();
$totalPrice = $pricer->getTotalPrice();
$optionsPrice = $pricer->getOptionsPrice();
Но я понятия не имею, как это сделать внутри Symfony / Doctrine, особенно способ доступа к Doctrine и репозиториям в контроллерах.
Ответ №1:
Вы правы в том, что вам следует передать всю свою бизнес-логику, пригодную для повторного использования, в службу, чтобы разные контроллеры могли повторно использовать код.
Вы ознакомились с документацией «как создать сервис»:
Документация по сервисному контейнеру
Я все же дам вам краткое описание скорости.
В конфигурации.yml вам нужно определить свой сервис:
services:
pricing_service:
class: AcmeProductBundleServicePricingService
arguments: [@doctrine]
Тогда вам просто нужно создать стандартный класс PHP bog для представления вашего сервиса:
namespace AcmeProductBundleService;
class PricingService {
private $doctrine;
function __construct($doctrine) {
$this->doctrine = $doctrine; // Note that this was injected using the arguments in the config.yml
}
// Now the rest of your functions go here such as "getUnitPrice" etc etc.
}
Наконец, чтобы получить свой сервис от контроллера, вам просто нужно сделать:
$pricingService = $this->get('pricing_service');
Есть другие способы, которыми вы можете модулировать службу, например, не выгружать все свои службы в config.yml, но все это объясняется в документации. Также обратите внимание, что вы можете внедрить в свой сервис любую другую услугу, которую пожелаете, поэтому, если вам нужны такие вещи, как arguments: [@doctrine, @security.context, @validator]
, вы можете сделать все это или даже: [@my_other_service]
.
Я подозреваю, что из вашего другого вопроса о внедрении EntityManager вы, возможно, уже поняли, что это правильный путь!
Надеюсь, это все еще было полезно для вас!
Комментарии:
1. Фантастика — спасибо! Между вашим ответом и ответом Куба я понимаю, как выполнить эту часть приложения.
2. что, если вы хотите объединить два вызова службы в рамках одной транзакции?
Ответ №2:
Вы упростили свой пример, так что я действительно не знаю всех деталей, но вот моя попытка решить вашу проблему.
Обратите внимание, что на самом деле вам может понадобиться более одной услуги, но вы должны получить представление, основанное на моем примере.
В основном следуйте принципу — один класс имеет одну ответственность.
Калькулятор цен вычисляет цену:
namespace MyNamespace;
class PriceCalculator
{
private $entityManager = null;
public function __construct(DoctrineORMEntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* @return PriceInterface
*/
public function calculate()
{
// do your stuff and return Price
}
}
Цена описывается интерфейсом PriceInterface:
namespace MyNamespace;
interface PriceInterface
{
public function getUnitPrice();
public function getTotalPrice();
public function getOptionsPrice();
}
Сервис калькулятора цен зависит от менеджера объектов:
my_namespace.price_calculator:
class: MyNamespacePriceCalculator
arguments: [ @doctrine.orm.default_entity_manager ]
Контроллер использует сервис калькулятора цен для получения цены:
public function indexAction()
{
$priceCalculator = $this->get('my_namespace.price_calculator');
$price = $priceCalculator->calculate();
$unitPrice = $price->getUnitPrice();
$totalPrice = $price->getTotalPrice();
$optionsPrice = $price->getOptionsPrice();
}
Если вам нужен запрос или другая услуга, вы можете ввести их с помощью DIC или вручную в качестве параметра для метода calculate().
Обратите внимание, что я ввел EntityManager в службу PriceCalculator, но вы могли бы определить поставщиков данных как службы и вместо этого вводить их (для действительно сложных вещей).
Вы также можете отправлять все запросы в репозитории и передавать объекты в свой калькулятор цен.
Комментарии:
1. Это, в сочетании с ответом Кашина, было очень полезно. Спасибо!