vendor/sylius/resource-bundle/src/Bundle/Controller/ResourceController.php line 112

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the Sylius package.
  4. *
  5. * (c) Paweł Jędrzejewski
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. declare(strict_types=1);
  11. namespace Sylius\Bundle\ResourceBundle\Controller;
  12. use Doctrine\Persistence\ObjectManager;
  13. use FOS\RestBundle\View\View;
  14. use Sylius\Bundle\ResourceBundle\Event\ResourceControllerEvent;
  15. use Sylius\Component\Resource\Exception\DeleteHandlingException;
  16. use Sylius\Component\Resource\Exception\UpdateHandlingException;
  17. use Sylius\Component\Resource\Factory\FactoryInterface;
  18. use Sylius\Component\Resource\Metadata\MetadataInterface;
  19. use Sylius\Component\Resource\Model\ResourceInterface;
  20. use Sylius\Component\Resource\Repository\RepositoryInterface;
  21. use Sylius\Component\Resource\ResourceActions;
  22. use Symfony\Component\DependencyInjection\ContainerAwareTrait;
  23. use Symfony\Component\DependencyInjection\ContainerInterface;
  24. use Symfony\Component\HttpFoundation\Request;
  25. use Symfony\Component\HttpFoundation\Response;
  26. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  27. use Symfony\Component\HttpKernel\Exception\HttpException;
  28. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  29. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  30. class ResourceController
  31. {
  32. use ControllerTrait;
  33. use ContainerAwareTrait;
  34. protected MetadataInterface $metadata;
  35. protected RequestConfigurationFactoryInterface $requestConfigurationFactory;
  36. protected ?ViewHandlerInterface $viewHandler;
  37. protected RepositoryInterface $repository;
  38. protected FactoryInterface $factory;
  39. protected NewResourceFactoryInterface $newResourceFactory;
  40. protected ObjectManager $manager;
  41. protected SingleResourceProviderInterface $singleResourceProvider;
  42. protected ResourcesCollectionProviderInterface $resourcesCollectionProvider;
  43. protected ResourceFormFactoryInterface $resourceFormFactory;
  44. protected RedirectHandlerInterface $redirectHandler;
  45. protected FlashHelperInterface $flashHelper;
  46. protected AuthorizationCheckerInterface $authorizationChecker;
  47. protected EventDispatcherInterface $eventDispatcher;
  48. protected ?StateMachineInterface $stateMachine;
  49. protected ResourceUpdateHandlerInterface $resourceUpdateHandler;
  50. protected ResourceDeleteHandlerInterface $resourceDeleteHandler;
  51. public function __construct(
  52. MetadataInterface $metadata,
  53. RequestConfigurationFactoryInterface $requestConfigurationFactory,
  54. ?ViewHandlerInterface $viewHandler,
  55. RepositoryInterface $repository,
  56. FactoryInterface $factory,
  57. NewResourceFactoryInterface $newResourceFactory,
  58. ObjectManager $manager,
  59. SingleResourceProviderInterface $singleResourceProvider,
  60. ResourcesCollectionProviderInterface $resourcesFinder,
  61. ResourceFormFactoryInterface $resourceFormFactory,
  62. RedirectHandlerInterface $redirectHandler,
  63. FlashHelperInterface $flashHelper,
  64. AuthorizationCheckerInterface $authorizationChecker,
  65. EventDispatcherInterface $eventDispatcher,
  66. ?StateMachineInterface $stateMachine,
  67. ResourceUpdateHandlerInterface $resourceUpdateHandler,
  68. ResourceDeleteHandlerInterface $resourceDeleteHandler,
  69. ) {
  70. $this->metadata = $metadata;
  71. $this->requestConfigurationFactory = $requestConfigurationFactory;
  72. $this->viewHandler = $viewHandler;
  73. $this->repository = $repository;
  74. $this->factory = $factory;
  75. $this->newResourceFactory = $newResourceFactory;
  76. $this->manager = $manager;
  77. $this->singleResourceProvider = $singleResourceProvider;
  78. $this->resourcesCollectionProvider = $resourcesFinder;
  79. $this->resourceFormFactory = $resourceFormFactory;
  80. $this->redirectHandler = $redirectHandler;
  81. $this->flashHelper = $flashHelper;
  82. $this->authorizationChecker = $authorizationChecker;
  83. $this->eventDispatcher = $eventDispatcher;
  84. $this->stateMachine = $stateMachine;
  85. $this->resourceUpdateHandler = $resourceUpdateHandler;
  86. $this->resourceDeleteHandler = $resourceDeleteHandler;
  87. }
  88. public function showAction(Request $request): Response
  89. {
  90. $configuration = $this->requestConfigurationFactory->create($this->metadata, $request);
  91. $this->isGrantedOr403($configuration, ResourceActions::SHOW);
  92. $resource = $this->findOr404($configuration);
  93. $event = $this->eventDispatcher->dispatch(ResourceActions::SHOW, $configuration, $resource);
  94. $eventResponse = $event->getResponse();
  95. if (null !== $eventResponse) {
  96. return $eventResponse;
  97. }
  98. if ($configuration->isHtmlRequest()) {
  99. return $this->render($configuration->getTemplate(ResourceActions::SHOW . '.html'), [
  100. 'configuration' => $configuration,
  101. 'metadata' => $this->metadata,
  102. 'resource' => $resource,
  103. $this->metadata->getName() => $resource,
  104. ]);
  105. }
  106. return $this->createRestView($configuration, $resource);
  107. }
  108. public function indexAction(Request $request): Response
  109. {
  110. $configuration = $this->requestConfigurationFactory->create($this->metadata, $request);
  111. $this->isGrantedOr403($configuration, ResourceActions::INDEX);
  112. $resources = $this->resourcesCollectionProvider->get($configuration, $this->repository);
  113. $event = $this->eventDispatcher->dispatchMultiple(ResourceActions::INDEX, $configuration, $resources);
  114. $eventResponse = $event->getResponse();
  115. if (null !== $eventResponse) {
  116. return $eventResponse;
  117. }
  118. if ($configuration->isHtmlRequest()) {
  119. return $this->render($configuration->getTemplate(ResourceActions::INDEX . '.html'), [
  120. 'configuration' => $configuration,
  121. 'metadata' => $this->metadata,
  122. 'resources' => $resources,
  123. $this->metadata->getPluralName() => $resources,
  124. ]);
  125. }
  126. return $this->createRestView($configuration, $resources);
  127. }
  128. public function createAction(Request $request): Response
  129. {
  130. $configuration = $this->requestConfigurationFactory->create($this->metadata, $request);
  131. $this->isGrantedOr403($configuration, ResourceActions::CREATE);
  132. $newResource = $this->newResourceFactory->create($configuration, $this->factory);
  133. $form = $this->resourceFormFactory->create($configuration, $newResource);
  134. $form->handleRequest($request);
  135. if ($request->isMethod('POST') && $form->isSubmitted() && $form->isValid()) {
  136. $newResource = $form->getData();
  137. $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::CREATE, $configuration, $newResource);
  138. if ($event->isStopped() && !$configuration->isHtmlRequest()) {
  139. throw new HttpException($event->getErrorCode(), $event->getMessage());
  140. }
  141. if ($event->isStopped()) {
  142. $this->flashHelper->addFlashFromEvent($configuration, $event);
  143. $eventResponse = $event->getResponse();
  144. if (null !== $eventResponse) {
  145. return $eventResponse;
  146. }
  147. return $this->redirectHandler->redirectToIndex($configuration, $newResource);
  148. }
  149. if ($configuration->hasStateMachine()) {
  150. $stateMachine = $this->getStateMachine();
  151. $stateMachine->apply($configuration, $newResource);
  152. }
  153. $this->repository->add($newResource);
  154. if ($configuration->isHtmlRequest()) {
  155. $this->flashHelper->addSuccessFlash($configuration, ResourceActions::CREATE, $newResource);
  156. }
  157. $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::CREATE, $configuration, $newResource);
  158. if (!$configuration->isHtmlRequest()) {
  159. return $this->createRestView($configuration, $newResource, Response::HTTP_CREATED);
  160. }
  161. $postEventResponse = $postEvent->getResponse();
  162. if (null !== $postEventResponse) {
  163. return $postEventResponse;
  164. }
  165. return $this->redirectHandler->redirectToResource($configuration, $newResource);
  166. }
  167. if ($request->isMethod('POST') && $form->isSubmitted() && !$form->isValid()) {
  168. $responseCode = Response::HTTP_UNPROCESSABLE_ENTITY;
  169. }
  170. if (!$configuration->isHtmlRequest()) {
  171. return $this->createRestView($configuration, $form, Response::HTTP_BAD_REQUEST);
  172. }
  173. $initializeEvent = $this->eventDispatcher->dispatchInitializeEvent(ResourceActions::CREATE, $configuration, $newResource);
  174. $initializeEventResponse = $initializeEvent->getResponse();
  175. if (null !== $initializeEventResponse) {
  176. return $initializeEventResponse;
  177. }
  178. return $this->render($configuration->getTemplate(ResourceActions::CREATE . '.html'), [
  179. 'configuration' => $configuration,
  180. 'metadata' => $this->metadata,
  181. 'resource' => $newResource,
  182. $this->metadata->getName() => $newResource,
  183. 'form' => $form->createView(),
  184. ], null, $responseCode ?? Response::HTTP_OK);
  185. }
  186. public function updateAction(Request $request): Response
  187. {
  188. $configuration = $this->requestConfigurationFactory->create($this->metadata, $request);
  189. $this->isGrantedOr403($configuration, ResourceActions::UPDATE);
  190. $resource = $this->findOr404($configuration);
  191. $form = $this->resourceFormFactory->create($configuration, $resource);
  192. $form->handleRequest($request);
  193. if (
  194. in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true) &&
  195. $form->isSubmitted() &&
  196. $form->isValid()
  197. ) {
  198. $resource = $form->getData();
  199. /** @var ResourceControllerEvent $event */
  200. $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::UPDATE, $configuration, $resource);
  201. if ($event->isStopped() && !$configuration->isHtmlRequest()) {
  202. throw new HttpException($event->getErrorCode(), $event->getMessage());
  203. }
  204. if ($event->isStopped()) {
  205. $this->flashHelper->addFlashFromEvent($configuration, $event);
  206. $eventResponse = $event->getResponse();
  207. if (null !== $eventResponse) {
  208. return $eventResponse;
  209. }
  210. return $this->redirectHandler->redirectToResource($configuration, $resource);
  211. }
  212. try {
  213. $this->resourceUpdateHandler->handle($resource, $configuration, $this->manager);
  214. } catch (UpdateHandlingException $exception) {
  215. if (!$configuration->isHtmlRequest()) {
  216. return $this->createRestView($configuration, $form, $exception->getApiResponseCode());
  217. }
  218. $this->flashHelper->addErrorFlash($configuration, $exception->getFlash());
  219. return $this->redirectHandler->redirectToReferer($configuration);
  220. }
  221. if ($configuration->isHtmlRequest()) {
  222. $this->flashHelper->addSuccessFlash($configuration, ResourceActions::UPDATE, $resource);
  223. }
  224. $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $configuration, $resource);
  225. if (!$configuration->isHtmlRequest()) {
  226. if ($configuration->getParameters()->get('return_content', false)) {
  227. return $this->createRestView($configuration, $resource, Response::HTTP_OK);
  228. }
  229. return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT);
  230. }
  231. $postEventResponse = $postEvent->getResponse();
  232. if (null !== $postEventResponse) {
  233. return $postEventResponse;
  234. }
  235. return $this->redirectHandler->redirectToResource($configuration, $resource);
  236. }
  237. if (in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true) && $form->isSubmitted() && !$form->isValid()) {
  238. $responseCode = Response::HTTP_UNPROCESSABLE_ENTITY;
  239. }
  240. if (!$configuration->isHtmlRequest()) {
  241. return $this->createRestView($configuration, $form, Response::HTTP_BAD_REQUEST);
  242. }
  243. $initializeEvent = $this->eventDispatcher->dispatchInitializeEvent(ResourceActions::UPDATE, $configuration, $resource);
  244. $initializeEventResponse = $initializeEvent->getResponse();
  245. if (null !== $initializeEventResponse) {
  246. return $initializeEventResponse;
  247. }
  248. return $this->render($configuration->getTemplate(ResourceActions::UPDATE . '.html'), [
  249. 'configuration' => $configuration,
  250. 'metadata' => $this->metadata,
  251. 'resource' => $resource,
  252. $this->metadata->getName() => $resource,
  253. 'form' => $form->createView(),
  254. ], null, $responseCode ?? Response::HTTP_OK);
  255. }
  256. public function deleteAction(Request $request): Response
  257. {
  258. $configuration = $this->requestConfigurationFactory->create($this->metadata, $request);
  259. $this->isGrantedOr403($configuration, ResourceActions::DELETE);
  260. $resource = $this->findOr404($configuration);
  261. if ($configuration->isCsrfProtectionEnabled() && !$this->isCsrfTokenValid((string) $resource->getId(), (string) $request->request->get('_csrf_token'))) {
  262. throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid csrf token.');
  263. }
  264. $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::DELETE, $configuration, $resource);
  265. if ($event->isStopped() && !$configuration->isHtmlRequest()) {
  266. throw new HttpException($event->getErrorCode(), $event->getMessage());
  267. }
  268. if ($event->isStopped()) {
  269. $this->flashHelper->addFlashFromEvent($configuration, $event);
  270. $eventResponse = $event->getResponse();
  271. if (null !== $eventResponse) {
  272. return $eventResponse;
  273. }
  274. return $this->redirectHandler->redirectToIndex($configuration, $resource);
  275. }
  276. try {
  277. $this->resourceDeleteHandler->handle($resource, $this->repository);
  278. } catch (DeleteHandlingException $exception) {
  279. if (!$configuration->isHtmlRequest()) {
  280. return $this->createRestView($configuration, null, $exception->getApiResponseCode());
  281. }
  282. $this->flashHelper->addErrorFlash($configuration, $exception->getFlash());
  283. return $this->redirectHandler->redirectToReferer($configuration);
  284. }
  285. if ($configuration->isHtmlRequest()) {
  286. $this->flashHelper->addSuccessFlash($configuration, ResourceActions::DELETE, $resource);
  287. }
  288. $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::DELETE, $configuration, $resource);
  289. if (!$configuration->isHtmlRequest()) {
  290. return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT);
  291. }
  292. $postEventResponse = $postEvent->getResponse();
  293. if (null !== $postEventResponse) {
  294. return $postEventResponse;
  295. }
  296. return $this->redirectHandler->redirectToIndex($configuration, $resource);
  297. }
  298. public function bulkDeleteAction(Request $request): Response
  299. {
  300. $configuration = $this->requestConfigurationFactory->create($this->metadata, $request);
  301. $this->isGrantedOr403($configuration, ResourceActions::BULK_DELETE);
  302. $resources = $this->resourcesCollectionProvider->get($configuration, $this->repository);
  303. if (
  304. $configuration->isCsrfProtectionEnabled() &&
  305. !$this->isCsrfTokenValid(ResourceActions::BULK_DELETE, (string) $request->request->get('_csrf_token'))
  306. ) {
  307. throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid csrf token.');
  308. }
  309. $this->eventDispatcher->dispatchMultiple(ResourceActions::BULK_DELETE, $configuration, $resources);
  310. foreach ($resources as $resource) {
  311. $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::DELETE, $configuration, $resource);
  312. if ($event->isStopped() && !$configuration->isHtmlRequest()) {
  313. throw new HttpException($event->getErrorCode(), $event->getMessage());
  314. }
  315. if ($event->isStopped()) {
  316. $this->flashHelper->addFlashFromEvent($configuration, $event);
  317. $eventResponse = $event->getResponse();
  318. if (null !== $eventResponse) {
  319. return $eventResponse;
  320. }
  321. return $this->redirectHandler->redirectToIndex($configuration, $resource);
  322. }
  323. try {
  324. $this->resourceDeleteHandler->handle($resource, $this->repository);
  325. } catch (DeleteHandlingException $exception) {
  326. if (!$configuration->isHtmlRequest()) {
  327. return $this->createRestView($configuration, null, $exception->getApiResponseCode());
  328. }
  329. $this->flashHelper->addErrorFlash($configuration, $exception->getFlash());
  330. return $this->redirectHandler->redirectToReferer($configuration);
  331. }
  332. $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::DELETE, $configuration, $resource);
  333. }
  334. if (!$configuration->isHtmlRequest()) {
  335. return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT);
  336. }
  337. $this->flashHelper->addSuccessFlash($configuration, ResourceActions::BULK_DELETE);
  338. if (isset($postEvent)) {
  339. $postEventResponse = $postEvent->getResponse();
  340. if (null !== $postEventResponse) {
  341. return $postEventResponse;
  342. }
  343. }
  344. return $this->redirectHandler->redirectToIndex($configuration);
  345. }
  346. public function applyStateMachineTransitionAction(Request $request): Response
  347. {
  348. $stateMachine = $this->getStateMachine();
  349. $configuration = $this->requestConfigurationFactory->create($this->metadata, $request);
  350. $this->isGrantedOr403($configuration, ResourceActions::UPDATE);
  351. $resource = $this->findOr404($configuration);
  352. if ($configuration->isCsrfProtectionEnabled() && !$this->isCsrfTokenValid((string) $resource->getId(), $request->get('_csrf_token'))) {
  353. throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid CSRF token.');
  354. }
  355. $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::UPDATE, $configuration, $resource);
  356. if ($event->isStopped() && !$configuration->isHtmlRequest()) {
  357. throw new HttpException($event->getErrorCode(), $event->getMessage());
  358. }
  359. if ($event->isStopped()) {
  360. $this->flashHelper->addFlashFromEvent($configuration, $event);
  361. $eventResponse = $event->getResponse();
  362. if (null !== $eventResponse) {
  363. return $eventResponse;
  364. }
  365. return $this->redirectHandler->redirectToResource($configuration, $resource);
  366. }
  367. if (!$stateMachine->can($configuration, $resource)) {
  368. throw new BadRequestHttpException();
  369. }
  370. try {
  371. $this->resourceUpdateHandler->handle($resource, $configuration, $this->manager);
  372. } catch (UpdateHandlingException $exception) {
  373. if (!$configuration->isHtmlRequest()) {
  374. return $this->createRestView($configuration, $resource, $exception->getApiResponseCode());
  375. }
  376. $this->flashHelper->addErrorFlash($configuration, $exception->getFlash());
  377. return $this->redirectHandler->redirectToReferer($configuration);
  378. }
  379. if ($configuration->isHtmlRequest()) {
  380. $this->flashHelper->addSuccessFlash($configuration, ResourceActions::UPDATE, $resource);
  381. }
  382. $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $configuration, $resource);
  383. if (!$configuration->isHtmlRequest()) {
  384. if ($configuration->getParameters()->get('return_content', true)) {
  385. return $this->createRestView($configuration, $resource, Response::HTTP_OK);
  386. }
  387. return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT);
  388. }
  389. $postEventResponse = $postEvent->getResponse();
  390. if (null !== $postEventResponse) {
  391. return $postEventResponse;
  392. }
  393. return $this->redirectHandler->redirectToResource($configuration, $resource);
  394. }
  395. /**
  396. * @return mixed
  397. */
  398. protected function getParameter(string $name)
  399. {
  400. if (!$this->container instanceof ContainerInterface) {
  401. throw new \RuntimeException(sprintf(
  402. 'Container passed to "%s" has to implements "%s".',
  403. self::class,
  404. ContainerInterface::class,
  405. ));
  406. }
  407. return $this->container->getParameter($name);
  408. }
  409. /**
  410. * @throws AccessDeniedException
  411. */
  412. protected function isGrantedOr403(RequestConfiguration $configuration, string $permission): void
  413. {
  414. if (!$configuration->hasPermission()) {
  415. return;
  416. }
  417. $permission = $configuration->getPermission($permission);
  418. if (!$this->authorizationChecker->isGranted($configuration, $permission)) {
  419. throw new AccessDeniedException();
  420. }
  421. }
  422. /**
  423. * @throws NotFoundHttpException
  424. */
  425. protected function findOr404(RequestConfiguration $configuration): ResourceInterface
  426. {
  427. if (null === $resource = $this->singleResourceProvider->get($configuration, $this->repository)) {
  428. throw new NotFoundHttpException(sprintf('The "%s" has not been found', $this->metadata->getHumanizedName()));
  429. }
  430. return $resource;
  431. }
  432. /**
  433. * @param mixed $data
  434. */
  435. protected function createRestView(RequestConfiguration $configuration, $data, int $statusCode = null): Response
  436. {
  437. if (null === $this->viewHandler) {
  438. throw new \LogicException('You can not use the "non-html" request if FriendsOfSymfony Rest Bundle is not available. Try running "composer require friendsofsymfony/rest-bundle".');
  439. }
  440. $view = View::create($data, $statusCode);
  441. return $this->viewHandler->handle($configuration, $view);
  442. }
  443. protected function getStateMachine(): StateMachineInterface
  444. {
  445. if (null === $this->stateMachine) {
  446. throw new \LogicException('You can not use the "state-machine" if Winzou State Machine Bundle is not available. Try running "composer require winzou/state-machine-bundle".');
  447. }
  448. return $this->stateMachine;
  449. }
  450. }