*/ abstract class CrudController extends AdminController { protected array $filters = []; protected array $sort = [ 'label' => null, 'direction' => null, ]; abstract protected function getConfiguration(): CrudConfiguration; protected function doIndex(int $page, RepositoryQuery $query, Request $request, Session $session, string $context = 'index'): Response { $configuration = $this->getConfiguration(); $this->applySort('index', $query, $request); $this->updateFilters($request, $session); $pager = $query ->usefilters($this->filters) ->paginate($page, $configuration->getMaxPerPage($context)) ; return $this->render($this->getConfiguration()->getView($context), [ 'configuration' => $configuration, 'pager' => $pager, 'sort' => $this->sort, 'context' => $context, 'filters' => [ 'show' => null !== $configuration->getForm('filter'), 'isEmpty' => empty($this->filters), ], ]); } protected function doNew(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeCreate = null, string $context = 'new'): Response { $configuration = $this->getConfiguration(); $this->prepareEntity($entity); $form = $this->createForm($configuration->getForm('new'), $entity, $configuration->getFormOptions($context)); if ($request->isMethod('POST')) { $form->handleRequest($request); if ($form->isValid()) { if (null !== $beforeCreate) { call_user_func_array($beforeCreate, [$entity, $form, $request]); } $entityManager->create($entity); $this->addFlash('success', 'The data has been saved.'); return $this->redirectToRoute($configuration->getPageRoute('edit'), array_merge( ['entity' => $entity->getId()], $configuration->getPageRouteParams('edit') )); } $this->addFlash('warning', 'The form is not valid.'); } return $this->render($configuration->getView($context), [ 'form' => $form->createView(), 'configuration' => $configuration, 'context' => $context, 'entity' => $entity, ]); } protected function doShow(EntityInterface $entity, string $context = 'show'): Response { $configuration = $this->getConfiguration(); return $this->render($configuration->getView($context), [ 'entity' => $entity, 'context' => $context, 'configuration' => $configuration, ]); } protected function doEdit(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeUpdate = null, string $context = 'edit'): Response { $configuration = $this->getConfiguration(); $this->prepareEntity($entity); $form = $this->createForm($configuration->getForm('edit'), $entity, $configuration->getFormOptions($context)); if ($request->isMethod('POST')) { $form->handleRequest($request); if ($form->isValid()) { if (null !== $beforeUpdate) { call_user_func_array($beforeUpdate, [$entity, $form, $request]); } $entityManager->update($entity); $this->addFlash('success', 'The data has been saved.'); return $this->redirectToRoute($configuration->getPageRoute($context), array_merge( ['entity' => $entity->getId()], $configuration->getPageRouteParams($context) )); } $this->addFlash('warning', 'The form is not valid.'); } return $this->render($configuration->getView($context), [ 'form' => $form->createView(), 'context' => $context, 'configuration' => $configuration, 'entity' => $entity, ]); } protected function doInlineEdit(string $context, string $label, EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeUpdate = null): Response { $configuration = $this->getConfiguration(); $this->prepareEntity($entity); $builder = $this->createFormBuilder($entity); $callback = $configuration->getFields($context)[$label]['options']['inline_form'] ?? null; $validationCallback = $configuration->getFields($context)[$label]['options']['inline_form_validation'] ?? null; if (null === $callback) { throw $this->createNotFoundException(); } call_user_func_array($callback, [$builder, $entity]); $redirectTo = $request->query->get('redirectTo'); $form = $builder->getForm(); $session = $request->getSession(); $lastRequestId = sprintf( 'inline_request_%s_%s_%s_%s', get_class($entity), $entity->getId(), $context, $label ); $lastRequest = $session->get($lastRequestId); if (null !== $lastRequest && !$request->isMethod('POST')) { $fakeRequest = Request::create( uri: $request->getUri(), method: 'POST', parameters: [$form->getName() => $lastRequest] ); $form->handleRequest($fakeRequest); if (null !== $validationCallback) { call_user_func_array($validationCallback, [$entity, $form, $request]); } $session->remove($lastRequestId); } if ($request->isMethod('POST')) { $form->handleRequest($request); if (null !== $validationCallback) { call_user_func_array($validationCallback, [$entity, $form, $request]); } if ($form->isValid()) { if (null !== $beforeUpdate) { call_user_func_array($beforeUpdate, [$entity, $form, $request]); } $session->remove($lastRequestId); $entityManager->update($entity); $this->addFlash('success', 'The data has been saved.'); return $this->redirect($redirectTo); } $session->set($lastRequestId, $request->request->get('form')); $this->addFlash('warning', 'The form is not valid.'); return $this->redirect(sprintf( '%s?data-modal=%s', $redirectTo, urlencode($request->getUri()) )); } return $this->render($configuration->getView('inline_edit'), [ 'form' => $form->createView(), 'configuration' => $configuration, 'entity' => $entity, 'context' => $context, 'label' => $label, 'redirectTo' => $redirectTo, ]); } protected function doSort(int $page, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response { $configuration = $this->getConfiguration(); $context = $request->query->get('context', 'index'); if (!$configuration->getIsSortableCollection($context)) { throw $this->createNotFoundException(); } $this->applySort($context, $query, $request); $this->updateFilters($request, $session); $pager = $query ->useFilters($this->filters) ->paginate($page, $configuration->getMaxPerPage($context)) ; if ($this->isCsrfTokenValid('sort', $request->query->get('_token'))) { $items = $request->request->get('items', []); $setter = 'set'.$configuration->getSortableCollectionProperty(); $orderStart = ($page - 1) * $configuration->getMaxPerPage($context); foreach ($pager as $key => $entity) { if (isset($items[$key + 1])) { $entity->{$setter}($items[$key + 1] + $orderStart); $entityManager->update($entity); } } $this->addFlash('success', 'The data has been saved.'); } else { $this->addFlash('warning', 'The form is not valid.'); } return $this->json([]); } protected function doBatch(int $page, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response { $configuration = $this->getConfiguration(); $datas = $request->request->get('batch', []); $context = $datas['context'] ?? 'index'; $target = $datas['target'] ?? null; $action = $datas['action'] ?? null; $token = $datas['_token'] ?? null; $items = $datas['items'] ?? []; $batchAction = $configuration->getBatchAction($context, $action); if (empty($context) || empty($action) || empty($target)) { return $this->json([]); } if (!$this->isCsrfTokenValid('batch', $token) || empty($batchAction)) { $this->addFlash('warning', 'The form is not valid.'); return $this->json([]); } $callback = $batchAction['callback']; $this->applySort($context, $query, $request); $this->updateFilters($request, $session); $query->useFilters($this->filters); $useSelection = 'selection' === $target; if ($batchAction['isGlobal']) { $selection = null; if ($useSelection) { $queryClone = clone $query; $pager = $queryClone->paginate($page, $configuration->getMaxPerPage($context)); $selection = []; foreach ($pager as $key => $entity) { if (isset($items[$key + 1])) { $selection[] = $entity; } } } $result = $callback($query, $entityManager, $selection); if ($result instanceof Response) { return $result; } return $this->redirect($request->query->get('redirectTo')); } $pager = $useSelection ? $query->paginate($page, $configuration->getMaxPerPage($context)) : $query->find() ; foreach ($pager as $key => $entity) { if (($useSelection && isset($items[$key + 1])) || !$useSelection) { $callback($entity, $entityManager); } } $this->addFlash('success', 'Batch action done.'); return $this->json([]); } protected function doDelete(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeDelete = null, string $route = 'index'): Response { $configuration = $this->getConfiguration(); if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) { if (null !== $beforeDelete) { call_user_func($beforeDelete, $entity); } $entityManager->delete($entity); $this->addFlash('success', 'The data has been removed.'); } return $this->redirectToRoute($configuration->getPageRoute($route)); } protected function doFilter(Session $session, string $context = 'filter'): Response { $configuration = $this->getConfiguration(); $type = $configuration->getForm('filter'); if (null === $type) { throw $this->createNotFoundException(); } $form = $this->createForm($type, null, $configuration->getFormOptions('filter')); $form->submit($session->get($form->getName(), [])); return $this->render($configuration->getView($context), [ 'form' => $form->createView(), 'context' => $context, 'configuration' => $configuration, ]); } protected function updateFilters(Request $request, Session $session) { $configuration = $this->getConfiguration(); $type = $configuration->getForm('filter'); if (null === $type) { return; } $form = $this->createForm($type, null, $configuration->getFormOptions('filter')); if ($request->query->has($form->getName())) { $filters = $request->query->get($form->getName()); if ('0' === $filters) { $filters = []; } } elseif ($session->has($form->getName())) { $filters = $session->get($form->getName()); } else { $filters = []; } $form->submit($filters); if (empty($filters)) { $this->filters = $filters; $session->set($form->getName(), $filters); } elseif ($form->isValid()) { $this->filters = $form->getData(); $session->set($form->getName(), $filters); } } protected function prepareEntity(EntityInterface $entity) { $configuration = $this->getConfiguration(); if ($configuration->isI18n()) { foreach ($configuration->getLocales() as $locale) { $entity->addTranslation($entity->translate($locale, false)); } } } protected function applySort(string $context, RepositoryQuery $query, Request $request) { $configuration = $this->getConfiguration(); if ($configuration->getIsSortableCollection($context)) { $query->orderBy(sprintf('.%s', $configuration->getSortableCollectionProperty())); return; } $defaultSort = $configuration->getDefaultSort($context); $session = $request->getSession(); $sessionId = sprintf('%s_%s_sort', $context, get_called_class()); $sessionSortName = sprintf('%s_label', $sessionId); $sessionSortDirection = sprintf('%s_direction', $sessionId); $name = $request->query->get('_sort', $session->get($sessionSortName)) ?? $defaultSort['label'] ?? null; $direction = strtolower( $request->query->get( '_sort_direction', $session->get($sessionSortDirection) ) ?? $defaultSort['direction'] ?? 'asc' ); $session->set($sessionSortName, $name); $session->set($sessionSortDirection, $direction); if (empty($name)) { return; } if (!in_array($direction, ['asc', 'desc'])) { $direction = 'asc'; } foreach ($configuration->getFields($context) as $label => $field) { $sortOption = $field['options']['sort'] ?? null; if (null === $sortOption) { continue; } if ($sortOption[0] !== $name) { continue; } $sorter = $sortOption[1]; if (is_string($sorter)) { $query->orderBy($sorter, $direction); } else { call_user_func_array($sorter, [$query, $direction]); } $this->sort = [ 'label' => $label, 'direction' => $direction, ]; return; } } }