From 3e38dbf65757ad9acff2039dcbfcced344e7ac94 Mon Sep 17 00:00:00 2001 From: Johannes Wachter Date: Tue, 24 Sep 2019 16:54:46 +0200 Subject: [PATCH] 10 - Add a form to add, edit or delete locations in the admin interface --- config/forms/location_details.xml | 53 +++++ config/packages/sulu_admin.yaml | 1 + config/services.yaml | 4 + src/Admin/LocationAdmin.php | 45 ++++- src/Controller/Admin/LocationController.php | 85 ++++++++ src/Repository/LocationRepository.php | 26 ++- src/Service/CountryCodeSelect.php | 27 +++ .../Admin/LocationControllerTest.php | 185 ++++++++++++++++++ tests/Functional/Traits/LocationTrait.php | 5 + 9 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 config/forms/location_details.xml create mode 100644 src/Service/CountryCodeSelect.php diff --git a/config/forms/location_details.xml b/config/forms/location_details.xml new file mode 100644 index 0000000..95df596 --- /dev/null +++ b/config/forms/location_details.xml @@ -0,0 +1,53 @@ + +
+ location_details + + + + + sulu_admin.name + + + + + + sulu_contact.street + + + + + + sulu_contact.number + + + + + + sulu_contact.zip + + + + + + sulu_contact.city + + + + + + sulu_contact.country + + + + + + + +
diff --git a/config/packages/sulu_admin.yaml b/config/packages/sulu_admin.yaml index f8caeb1..46a5f0e 100644 --- a/config/packages/sulu_admin.yaml +++ b/config/packages/sulu_admin.yaml @@ -21,6 +21,7 @@ sulu_admin: locations: routes: list: app.get_locations + detail: app.get_location # Registering Selection Field Types in this section field_type_options: diff --git a/config/services.yaml b/config/services.yaml index 86b2bda..6ce490d 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -49,6 +49,10 @@ services: bind: $repository: '@App\Repository\EventRepository' + app.country_select: + class: App\Service\CountryCodeSelect + public: true + # following service definitions will be removed in the website context of sulu App\Admin\: resource: '../src/Admin' diff --git a/src/Admin/LocationAdmin.php b/src/Admin/LocationAdmin.php index f713a57..fed0ad9 100644 --- a/src/Admin/LocationAdmin.php +++ b/src/Admin/LocationAdmin.php @@ -8,6 +8,7 @@ use Sulu\Bundle\AdminBundle\Admin\Admin; use Sulu\Bundle\AdminBundle\Admin\Navigation\NavigationItem; use Sulu\Bundle\AdminBundle\Admin\Navigation\NavigationItemCollection; +use Sulu\Bundle\AdminBundle\Admin\View\ToolbarAction; use Sulu\Bundle\AdminBundle\Admin\View\ViewBuilderFactoryInterface; use Sulu\Bundle\AdminBundle\Admin\View\ViewCollection; @@ -17,6 +18,10 @@ class LocationAdmin extends Admin const LOCATION_LIST_VIEW = 'app.locations_list'; + const LOCATION_ADD_FORM_VIEW = 'app.location_add_form'; + + const LOCATION_EDIT_FORM_VIEW = 'app.location_edit_form'; + /** * @var ViewBuilderFactoryInterface */ @@ -40,12 +45,50 @@ public function configureNavigationItems(NavigationItemCollection $navigationIte public function configureViews(ViewCollection $viewCollection): void { + $listToolbarActions = [ + new ToolbarAction('sulu_admin.add'), + new ToolbarAction('sulu_admin.delete'), + ]; $listView = $this->viewBuilderFactory->createListViewBuilder(self::LOCATION_LIST_VIEW, '/locations') ->setResourceKey(Location::RESOURCE_KEY) ->setListKey(self::LOCATION_LIST_KEY) ->setTitle('app.locations') ->addListAdapters(['table']) - ->addToolbarActions([]); + ->setAddView(static::LOCATION_ADD_FORM_VIEW) + ->setEditView(static::LOCATION_EDIT_FORM_VIEW) + ->addToolbarActions($listToolbarActions); $viewCollection->add($listView); + + $addFormView = $this->viewBuilderFactory->createResourceTabViewBuilder(self::LOCATION_ADD_FORM_VIEW, '/locations/add') + ->setResourceKey('locations') + ->setBackView(static::LOCATION_LIST_VIEW); + $viewCollection->add($addFormView); + + $addDetailsFormView = $this->viewBuilderFactory->createFormViewBuilder(self::LOCATION_ADD_FORM_VIEW . '.details', '/details') + ->setResourceKey('locations') + ->setFormKey('location_details') + ->setTabTitle('sulu_admin.details') + ->setEditView(static::LOCATION_EDIT_FORM_VIEW) + ->addToolbarActions([new ToolbarAction('sulu_admin.save')]) + ->setParent(static::LOCATION_ADD_FORM_VIEW); + $viewCollection->add($addDetailsFormView); + + $editFormView = $this->viewBuilderFactory->createResourceTabViewBuilder(static::LOCATION_EDIT_FORM_VIEW, '/locations/:id') + ->setResourceKey('locations') + ->setBackView(static::LOCATION_LIST_VIEW) + ->setTitleProperty('title'); + $viewCollection->add($editFormView); + + $formToolbarActions = [ + new ToolbarAction('sulu_admin.save'), + new ToolbarAction('sulu_admin.delete'), + ]; + $editDetailsFormView = $this->viewBuilderFactory->createFormViewBuilder(static::LOCATION_EDIT_FORM_VIEW . '.details', '/details') + ->setResourceKey('locations') + ->setFormKey('location_details') + ->setTabTitle('sulu_admin.details') + ->addToolbarActions($formToolbarActions) + ->setParent(static::LOCATION_EDIT_FORM_VIEW); + $viewCollection->add($editDetailsFormView); } } diff --git a/src/Controller/Admin/LocationController.php b/src/Controller/Admin/LocationController.php index cda7c1f..92acc06 100644 --- a/src/Controller/Admin/LocationController.php +++ b/src/Controller/Admin/LocationController.php @@ -6,11 +6,14 @@ use App\Common\DoctrineListRepresentationFactory; use App\Entity\Location; +use App\Repository\LocationRepository; use FOS\RestBundle\Controller\Annotations\RouteResource; use FOS\RestBundle\Routing\ClassResourceInterface; use FOS\RestBundle\View\ViewHandlerInterface; use Sulu\Component\Rest\AbstractRestController; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; /** @@ -23,12 +26,19 @@ class LocationController extends AbstractRestController implements ClassResource */ private $doctrineListRepresentationFactory; + /** + * @var LocationRepository + */ + private $repository; + public function __construct( DoctrineListRepresentationFactory $doctrineListRepresentationFactory, + LocationRepository $repository, ViewHandlerInterface $viewHandler, ?TokenStorageInterface $tokenStorage = null ) { $this->doctrineListRepresentationFactory = $doctrineListRepresentationFactory; + $this->repository = $repository; parent::__construct($viewHandler, $tokenStorage); } @@ -41,4 +51,79 @@ public function cgetAction(): Response return $this->handleView($this->view($listRepresentation)); } + + public function getAction(int $id, Request $request): Response + { + $entity = $this->load($id); + if (!$entity) { + throw new NotFoundHttpException(); + } + + return $this->handleView($this->view($entity)); + } + + public function postAction(Request $request): Response + { + $entity = $this->create(); + + $this->mapDataToEntity($request->request->all(), $entity); + + $this->save($entity); + + return $this->handleView($this->view($entity)); + } + + public function putAction(int $id, Request $request): Response + { + $entity = $this->load($id); + if (!$entity) { + throw new NotFoundHttpException(); + } + + $this->mapDataToEntity($request->request->all(), $entity); + + $this->save($entity); + + return $this->handleView($this->view($entity)); + } + + public function deleteAction(int $id): Response + { + $this->remove($id); + + return $this->handleView($this->view()); + } + + /** + * @param string[] $data + */ + protected function mapDataToEntity(array $data, Location $entity): void + { + $entity->setName($data['name']); + $entity->setStreet($data['street'] ?? ''); + $entity->setNumber($data['number'] ?? ''); + $entity->setCity($data['city'] ?? ''); + $entity->setPostalCode($data['postalCode'] ?? ''); + $entity->setCountryCode($data['countryCode'] ?? ''); + } + + protected function load(int $id): ?Location + { + return $this->repository->findById($id); + } + + protected function create(): Location + { + return $this->repository->create(); + } + + protected function save(Location $entity): void + { + $this->repository->save($entity); + } + + protected function remove(int $id): void + { + $this->repository->remove($id); + } } diff --git a/src/Repository/LocationRepository.php b/src/Repository/LocationRepository.php index 85ce921..af2389f 100644 --- a/src/Repository/LocationRepository.php +++ b/src/Repository/LocationRepository.php @@ -25,9 +25,33 @@ public function __construct(ManagerRegistry $registry) public function create(): Location { - $location = new Location(); + return new Location(); + } + + public function remove(int $id): void + { + /** @var object $location */ + $location = $this->getEntityManager()->getReference( + $this->getClassName(), + $id + ); + + $this->getEntityManager()->remove($location); + $this->getEntityManager()->flush(); + } + public function save(Location $location): void + { $this->getEntityManager()->persist($location); + $this->getEntityManager()->flush(); + } + + public function findById(int $id): ?Location + { + $location = $this->find($id); + if (!$location) { + return null; + } return $location; } diff --git a/src/Service/CountryCodeSelect.php b/src/Service/CountryCodeSelect.php new file mode 100644 index 0000000..3323d5d --- /dev/null +++ b/src/Service/CountryCodeSelect.php @@ -0,0 +1,27 @@ + $title) { + $values[] = [ + 'name' => $code, + 'title' => $title, + ]; + } + + return $values; + } +} diff --git a/tests/Functional/Controller/Admin/LocationControllerTest.php b/tests/Functional/Controller/Admin/LocationControllerTest.php index 24d494d..20ce674 100644 --- a/tests/Functional/Controller/Admin/LocationControllerTest.php +++ b/tests/Functional/Controller/Admin/LocationControllerTest.php @@ -46,4 +46,189 @@ public function testCGet(): void $this->assertSame($location1->getName(), $items[0]['name']); $this->assertSame($location2->getName(), $items[1]['name']); } + + public function testGet(): void + { + $location = $this->createLocation('Sulu'); + + $this->client->request('GET', '/admin/api/locations/' . $location->getId()); + + $response = $this->client->getResponse(); + $this->assertInstanceOf(Response::class, $response); + $result = json_decode($response->getContent() ?: '', true); + $this->assertHttpStatusCode(200, $response); + + $this->assertSame($location->getId(), $result['id']); + $this->assertSame($location->getName(), $result['name']); + } + + public function testPost(): void + { + $this->client->request( + 'POST', + '/admin/api/locations', + [ + 'name' => 'Sulu', + 'street' => 'Teststreet', + 'number' => '42', + 'postalCode' => '6850', + 'city' => 'Dornbirn', + 'countryCode' => 'AT', + ] + ); + + $response = $this->client->getResponse(); + $this->assertInstanceOf(Response::class, $response); + $result = json_decode($response->getContent() ?: '', true); + $this->assertHttpStatusCode(200, $response); + + $this->assertArrayHasKey('id', $result); + $this->assertNotNull($result['id']); + $this->assertSame('Sulu', $result['name']); + $this->assertSame('Teststreet', $result['street']); + $this->assertSame('42', $result['number']); + $this->assertSame('6850', $result['postalCode']); + $this->assertSame('Dornbirn', $result['city']); + $this->assertSame('AT', $result['countryCode']); + + $result = $this->findLocationById($result['id']); + + $this->assertNotNull($result); + $this->assertSame('Sulu', $result->getName()); + $this->assertSame('Teststreet', $result->getStreet()); + $this->assertSame('42', $result->getNumber()); + $this->assertSame('6850', $result->getPostalCode()); + $this->assertSame('Dornbirn', $result->getCity()); + $this->assertSame('AT', $result->getCountryCode()); + } + + public function testPostNullValues(): void + { + $this->client->request( + 'POST', + '/admin/api/locations', + [ + 'name' => 'Sulu', + ] + ); + + $response = $this->client->getResponse(); + $this->assertInstanceOf(Response::class, $response); + $result = json_decode($response->getContent() ?: '', true); + $this->assertHttpStatusCode(200, $response); + + $this->assertArrayHasKey('id', $result); + $this->assertNotNull($result['id']); + $this->assertSame('Sulu', $result['name']); + $this->assertEmpty($result['street']); + $this->assertEmpty($result['number']); + $this->assertEmpty($result['postalCode']); + $this->assertEmpty($result['city']); + $this->assertEmpty($result['countryCode']); + + $result = $this->findLocationById($result['id']); + + $this->assertNotNull($result); + $this->assertSame('Sulu', $result->getName()); + $this->assertEmpty($result->getStreet()); + $this->assertEmpty($result->getNumber()); + $this->assertEmpty($result->getPostalCode()); + $this->assertEmpty($result->getCity()); + $this->assertEmpty($result->getCountryCode()); + } + + public function testPut(): void + { + $location = $this->createLocation('Symfony'); + + $this->client->request( + 'PUT', + '/admin/api/locations/' . $location->getId(), + [ + 'name' => 'Sulu', + 'street' => 'Teststreet', + 'number' => '42', + 'postalCode' => '6850', + 'city' => 'Dornbirn', + 'countryCode' => 'AT', + ] + ); + + $response = $this->client->getResponse(); + $this->assertInstanceOf(Response::class, $response); + $result = json_decode($response->getContent() ?: '', true); + $this->assertHttpStatusCode(200, $response); + + $this->assertArrayHasKey('id', $result); + $this->assertNotNull($result['id']); + $this->assertSame('Sulu', $result['name']); + $this->assertSame('Teststreet', $result['street']); + $this->assertSame('42', $result['number']); + $this->assertSame('6850', $result['postalCode']); + $this->assertSame('Dornbirn', $result['city']); + $this->assertSame('AT', $result['countryCode']); + + $result = $this->findLocationById($result['id']); + + $this->assertNotNull($result); + $this->assertSame('Sulu', $result->getName()); + $this->assertSame('Teststreet', $result->getStreet()); + $this->assertSame('42', $result->getNumber()); + $this->assertSame('6850', $result->getPostalCode()); + $this->assertSame('Dornbirn', $result->getCity()); + $this->assertSame('AT', $result->getCountryCode()); + } + + public function testPutNullValues(): void + { + $location = $this->createLocation('Symfony'); + + $this->client->request( + 'PUT', + '/admin/api/locations/' . $location->getId(), + [ + 'name' => 'Sulu', + ] + ); + + $response = $this->client->getResponse(); + $this->assertInstanceOf(Response::class, $response); + $result = json_decode($response->getContent() ?: '', true); + $this->assertHttpStatusCode(200, $response); + + $this->assertArrayHasKey('id', $result); + $this->assertNotNull($result['id']); + $this->assertSame('Sulu', $result['name']); + $this->assertEmpty($result['street']); + $this->assertEmpty($result['number']); + $this->assertEmpty($result['postalCode']); + $this->assertEmpty($result['city']); + $this->assertEmpty($result['countryCode']); + + $result = $this->findLocationById($result['id']); + + $this->assertNotNull($result); + $this->assertSame('Sulu', $result->getName()); + $this->assertEmpty($result->getStreet()); + $this->assertEmpty($result->getNumber()); + $this->assertEmpty($result->getPostalCode()); + $this->assertEmpty($result->getCity()); + $this->assertEmpty($result->getCountryCode()); + } + + public function testDelete(): void + { + $location = $this->createLocation('Symfony'); + + /** @var int $locationId */ + $locationId = $location->getId(); + + $this->client->request('DELETE', '/admin/api/locations/' . $location->getId()); + + $response = $this->client->getResponse(); + $this->assertInstanceOf(Response::class, $response); + $this->assertHttpStatusCode(204, $response); + + $this->assertNull($this->findLocationById($locationId)); + } } diff --git a/tests/Functional/Traits/LocationTrait.php b/tests/Functional/Traits/LocationTrait.php index bc8304c..c871afd 100644 --- a/tests/Functional/Traits/LocationTrait.php +++ b/tests/Functional/Traits/LocationTrait.php @@ -26,6 +26,11 @@ public function createLocation(string $name): Location return $location; } + public function findLocationById(int $id): ?Location + { + return $this->getLocationRepository()->findById($id); + } + protected function getLocationRepository(): LocationRepository { return $this->getEntityManager()->getRepository(Location::class);