Symfony: где делать flush: в контролелре или сервисе?
Приведу пример кода, для пояснения дилемы:
1 вариант:
ArticleController
{
public function createArticle(Request $request, ArticleService $articeService, UserService $userService, EntityManager $em){
$articeService->create(CreateArticleDTO::createFromRequest($request));
$userService->incrementArticleCount($this->getUser());
$em->flush();
}
}
ArticleService{
public function (CreateArticleDTO $dto)
{
/// create $entity
$this->entityManager->persist($entity);
}
}
UserService{
public function (User $user)
{
$user->setArticelCount($user->getArticelCount()+1);
}
}
2 вариант.
ArticleController
{
public function createArticle(Request $request, ArticleService $articeService, UserService $userService){
$articeService->create(CreateArticleDTO::createFromRequest($request));
$userService->incrementArticleCount($this->getUser());
}
}
ArticleService{
public function (CreateArticleDTO $dto)
{
/// create $entity
$this->entityManager->persist($entity);
$this->entityManager->flush();
}
}
UserService{
public function (User $user)
{
$user->setArticelCount($user->getArticelCount()+1);
$this->entityManager->flush();
}
}
1 Варинт.
За: Где то видел, что это было в лучших практиках. Но воможно это было в эпоху 2 симфони, а возможно я вообще ошибаюсь. Смысл - что все сущности сохранятся в однйо транзакции. Или всё или ничего.
Против: занижаем связность. Т.е. нельзя быть уверенным, что вызвав данный сервис - статья сохранится. Придет другой разработчик, увидит название, вызовет - а статья не сохраняется. В тестировании надо отдельнов вызывать flush, чтобы проверить что статья сохранится. Вроде тестируем сервис, а вроде нужно что-то поверх него вызывать, чтобы проверить работоспособность. Если будет пакетное сохранение, то все равно придется вызывать flush в сервисах - архитектура будет не единой.
2) Вараинт: ну единственный минус - это несколько транзакций вместо одной. Что вроде как против тех же лучших практик. Возможно что-то еще, чего я не знаю.
Ответы (1 шт):
На мой взгляд, все таки, как я писал в комментариях, лучше использовать репозиторий по его прямому назначению, однако ваш подход с выполнением всего в контроллере, нарушает принцип единственной обязанности, в будущем, с таким кодом вы это, возможно прочувствуете. Я накидал кода, как пример, подчерпнуть для себя что-то или нет - решать вам. В данном конкретном случае, сохранение произойдет каскадом, ну и опять же, получается, каждая часть отвечает за что-то одно. Как следствие, пропадает необходимость в хранимом значении articlesCount.
<?php
// entity
class User
{
/**
* @var int
*
* @ORM\Column(type="integer", options={"unsigned": true})
* @ORM\Id()
* @ORM\GeneratedValue()
*/
private $id;
/**
* @var \Doctrine\Common\Collections\Collection<Article>
*
* @ORM\ManyToOne(targetEntity="Article", cascade={"persist"}, inversedBy="user")
*/
private $articles;
public function __construct()
{
$this->articles = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addArticle(Article $article): void
{
$this->articles->add($article);
}
public function getArticlesCount(): int
{
return $this->articles->count();
}
}
// entity
class Article
{
/**
* @var User
*
* @ORM\OneToMany(targetEntity="User", mappedBy="articles", cascade={"persist"})
* @ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
private $vsyakiyParam1;
private $vsyakiyParam2;
public function __construct(User $user, array $vsyakieParametry)
{
$this->user = $user;
$this->vsyakiyParam1 = $vsyakieParametry['vsyakiyParam1'] ?? null;
$this->vsyakiyParam2 = $vsyakieParametry['vsyakiyParam2'] ?? null;
}
}
class ArticleController
{
public function createArticle(Request $request, ArticleService $articeService)
{
$articeService->create($this->getUser(), CreateArticleDTO::createFromRequest($request));
}
}
class ArticleService
{
/**
* @var ArticleRepository
*/
private $articleRepository;
public function __construct(ArticleRepository $articleRepository)
{
$this->articleRepository = $articleRepository;
}
public function create(User $user, CreateArticleDTO $dto)
{
$articleEntity = new Article($user, $dto);
$this->articleRepository->save($articleEntity);
}
}
class ArticleRepository
{
/**
* @var EntityManager
*/
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function save(Article $article): void
{
$this->em->persist($article);
$this->em->flush();
}
}