Tulisan saya kali ini masih melanjutkan tulisan saya sebelumnya tentang Symfony 4. Saya harap anda sudah membaca tulisan saya tentang membuat API CRUD di Symfony https://catatan-pemrograman.blogspot.com/2018/09/membuat-api-crud-di-symfony-4.html dan Validasi https://catatan-pemrograman.blogspot.com/2018/09/validasi-di-symfony-4.html. Tulisan ini merupakan lanjutan dari tulisan saya sebelumnya tentang membangun API di Symfony.
Jika tulisan saya sebelumnya, saya memberikan contoh membuat API secara manual, kali ini saya lanjutkan dengan memanfaatkan library pihak ketika yang bernama FosRestBundle.
FosRestBundle
Dengan menggunakan FosRestBundle, kita dapat menghemat waktu untuk membuat REST API. Dan untuk dapat menggunakan FosRestBundle, kita terlebih dahulu harus menginstal serialize di symfony. Untuk kasus ini saya menggunakan JMSSerializerBundle. Jalankan perintah berikut menggunakan CMD di folder project symfony anda :
composer require jms/serializer-bundle
setelah berhasil melakukan instalasi JMSSerializerBundle, kemudian jalankan perintah berikut untuk instalasi FosRestBundle:
composer require friendsofsymfony/rest-bundle
setelah selesai proses instalasi, silahkan buka file fos_rest.yaml di config/packages/fos_rest.yaml. dan ubah menjadi seperti berikut:
# Read the documentation: https://symfony.com/doc/master/bundles/FOSRestBundle/index.html
fos_rest:
param_fetcher_listener: true
allowed_methods_listener: true
routing_loader:
default_format: json
# view:
# view_response_listener: 'force'
# formats:
# json: true
# exception:
# codes:
# App\Exception\MyException: 403
# messages:
# App\Exception\MyException: Forbidden area.
format_listener:
rules:
- { path: ^/api, prefer_extension: true, fallback_format: json, priorities: [ json ] }
oke, kita lanjut ke bagian Controller.
Controller
Sebagai pengingat, berikut saya tampilkan kode ApiController yang saya pakai pada tulisan saya sebelumnya:
pertama, saya akan ubah pada function index(). Coba lihat, kita belum punya kode untuk melakukan paging pada data yang akan kita tampilkan. Kenapa paging penting ? karena saat menampilkan data yang berjumlah banyak, akan memakan waktu yang lebih lama untuk melakukan operasi ke database dan proses menampilkannya. Jadi dengan adanya paging, kita bisa lebih menghemat waktu. Oke langsung saja, untuk menggunakan paging, saya memakai Pagerfanta. Untuk instalasi Pagerfanta, silahkan jalankan perintah berikut:
composer require white-october/pagerfanta-bundle
oke, setelah instalasi selesai, kita lanjut membuat file BaseController yang kita gunakan sebagai parent controller untuk controller yang akan kita buat. Silahkan buat folder baru dengan nama Base di dalam folder src/Controller. Kemudian buat file interface baru bernama BaseControllerInterface.php seperti berikut:
<?php // src/Controller/Base/BaseControllerInterface.php
namespace App\Controller\Base;
use Pagerfanta\Pagerfanta;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
/**
* Description of BaseControllerInterface
*
* @author NUR HIDAYAT
*/
interface BaseControllerInterface {
public function createFormApi(string $type, $data = null, array $options = array());
public function getPaginationData(PagerFanta $paginator);
public function getResponseSuccess($datas = array(), $response = Response::HTTP_OK);
public function createPaginator(Request $request, Query $query): Pagerfanta;
public function getQueryBuilder(string $entityClassName): QueryBuilder;
}
kemudian buat file BaseController.php seperti berikut:
<?php // src/Controller/Base/BaseController.php
namespace App\Controller\Base;
use FOS\RestBundle\Controller\FOSRestController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Form;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Query;
use Pagerfanta\Adapter\DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
use App\Resource\PaginationResource;
use App\Service\SerializationService;
use App\Controller\Base\BaseControllerInterface;
use JMS\Serializer\SerializerInterface;
use JMS\Serializer\SerializationContext;
/**
* Description of BaseController
*
* @author NUR HIDAYAT
*/
class BaseController extends FOSRestController implements BaseControllerInterface{ // kita extends class FOSRestController
/**
* @var EntityManager
*/
protected $entityManager;
/**
* @var SerializerInterface
*/
protected $serializer;
/**
* @var SerializationService
*/
protected $serializationService;
/*
* @var limit
*/
protected $limit = 10;
public function __construct(EntityManagerInterface $entityManager, SerializerInterface $serializer, SerializationService $serializationService)
{
$this->entityManager = $entityManager;
$this->serializer = $serializer;
$this->serializationService = $serializationService;
}
/**
* @param type Form
*/
public function createFormApi(string $type, $data = null, array $options = array()) // untuk membuat form
{
}
protected function processForm($obj, Form $form, Request $request) // untuk memproses form
{
}
protected function getErrors($form) // untuk mendapatkan error form
{
}
/**
*
* @param type $paginator
* @return PagerFanta
*/
public function getPaginationData(PagerFanta $paginator) // mengambil data dari paginator
{
}
/**
*
* @param type $data
* @param type $response
* @return FOS\Controller\ControllerTrait
*/
public function getResponseSuccess($datas = array(), $response = Response::HTTP_OK) // menghasilkan Response berupa JSON
{
}
/**
* @param Request $request
* @param Query $query
*
* @return Pagerfanta
*/
public function createPaginator(Request $request, Query $query): Pagerfanta // membuat pagingation
{
}
/**
* {@internal}.
*
* @since Entities are messed up as hell!
*
* @param string $entityClassName
*
* @return QueryBuilder
*/
public function getQueryBuilder(string $entityClassName): QueryBuilder // function untuk membuat query ke database
{
}
/**
* @param array $data
* @param SerializationContext $context
*
* @return array
*/
protected function serialize($data, SerializationContext $context = null): array
{
}
}
pada kode diatas, masing-masing sudah saya sertakan penjelasan kode. Pada tulisan saya sebelumnya, saya belum menggunakan form untuk menangkap inputan dari user. Kali inis aya menggunakan form dan untuk instalasi symfony form silahkan jalankan perintah berikut:
composer require symfony/formuntuk lebih paham mengenai form, silahkan baca dokumentasinya di https://symfony.com/doc/current/forms.html. Oke sekarang kita lanjut ke controller.
tambahkan kode berikut pada header class ApiController :
use App\Controller\Base\BaseController;
kemudian ubah class controller menjadi seperti berikut:
class ApiController extends BaseController
kemudian ubah function index menjadi seperti berikut:
/**
* @Route("/api", name="api")
*/
public function index(Request $request)
{
$queryBuilder = $this->getQueryBuilder(MCategory::class);
$paginator = $this->createPaginator($request, $queryBuilder->getQuery());
return $this->getResponseSuccess($this->getPaginationData($paginator));
}
sehingga controller ApiController menjadi seperti berikut:
<?php // src/Controller/ApiController.php
namespace App\Controller;
use App\Entity\MCategory;
use App\Controller\Base\BaseController;
use App\Form\MCategoryType;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class ApiController extends BaseController
{
/**
* @Route("/api", name="api")
*/
public function index(Request $request)
{
$queryBuilder = $this->getQueryBuilder(MCategory::class);
$paginator = $this->createPaginator($request, $queryBuilder->getQuery());
return $this->getResponseSuccess($this->getPaginationData($paginator));
}
/**
* @Route("/api/insert", name="api_category_insert", methods={"GET","POST"})
*/
public function insert(Request $request, ValidatorInterface $validator)
{
}
/**
* @Route("/api/update/{id}", name="api_category_update", methods={"GET","POST"})
*/
public function update(MCategory $MCategory, Request $request)
{
}
/**
* @Route("/api/delete/{id}", name="api_category_delete", methods={"DELETE"})
*/
public function deleted(MCategory $MCategory)
{
}
}
- getQueryBuilder
public function getQueryBuilder(string $entityClassName): QueryBuilder
{
$queryBuilder = $this->entityManager->createQueryBuilder()
->select('this')
->from($entityClassName, 'this');
return $queryBuilder;
}
- createPaginator
public function createPaginator(Request $request, Query $query): Pagerfanta
{
// Construct the doctrine adapter using the query.
$adapter = new DoctrineORMAdapter($query);
$paginator = new Pagerfanta($adapter);
$paginator->setAllowOutOfRangePages(true);
// Set pages based on the request parameters.
$paginator->setMaxPerPage($request->query->get('limit', $this->limit));
$paginator->setCurrentPage($request->query->get('page', 1));
return $paginator;
}
- getPaginationData
public function getPaginationData(PagerFanta $paginator)
{
$data = $this->serialize(
$paginator->getIterator()->getArrayCopy(),
$this->serializationService->createBaseOnRequest()
);
$datas['data'] = $data;
$pagination = PaginationResource::createFromPagerfanta($paginator);
if($pagination) {
$datas['pagination'] = $pagination->toJsArray();
}
return $datas;
}
- getResponseSuccess
public function getResponseSuccess($datas = array(), $response = Response::HTTP_OK)
{
$view = $this->view( $datas, $response);
return $this->handleView($view);
}
kemudian buat folder baru bernama Resouce di dalam folder src, dan buat class baru bernama PaginationResource.php seperti berikut:
<?php // src/Resource/PaginationResource.php
declare(strict_types=1);
namespace App\Resource;
use Pagerfanta\Pagerfanta;
final class PaginationResource
{
/**
* @var int
*/
private $totalNumberOfResults;
/**
* @var int
*/
private $resultsPerPageCount;
/**
* @var int
*/
private $currentPageNumber;
/**
* @var int
*/
private $totalPageNumber;
/**
* PaginationResource constructor.
*
* @param int $totalNumberOfResults
* @param int $resultsPerPageCount
* @param int $currentPageNumber
*/
public function __construct(int $totalNumberOfResults = 0, int $resultsPerPageCount = 0, int $currentPageNumber = 0, int $totalPageNumber)
{
$this->totalNumberOfResults = $totalNumberOfResults;
$this->resultsPerPageCount = $resultsPerPageCount;
$this->currentPageNumber = $currentPageNumber;
$this->totalPageNumber = $totalPageNumber;
}
/**
* @param Pagerfanta $paginator
*
* @return self
*/
public static function createFromPagerfanta(Pagerfanta $paginator): self
{
return new self(
$paginator->getNbResults(),
$paginator->getMaxPerPage(),
$paginator->getCurrentPage(),
$paginator->getNbPages()
);
}
/**
* @return array
*/
public function toJsArray(): array
{
return [
'total' => $this->totalNumberOfResults,
'limit' => $this->resultsPerPageCount,
'page' => $this->currentPageNumber,
'total_page' => $this->totalPageNumber
];
}
}
kemudian buat folder baru bernama Service di dalam folder src, dan buat class baru bernama SerializationService.php seperti berikut:
<?php // src/Service/SerializationService.php
declare(strict_types=1);
namespace App\Service;
use JMS\Serializer\SerializationContext;
use Symfony\Component\HttpFoundation\RequestStack;
class SerializationService
{
/**
* @var RequestStack
*/
private $requestStack;
/**
* SerializationService constructor.
*
* @param RequestStack $requestStack
*/
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
/**
* @return SerializationContext
*/
public function createBaseOnRequest(): SerializationContext
{
$currentRequest = $this->requestStack->getCurrentRequest();
if (false === !!$currentRequest) {
return new SerializationContext();
}
//$expand = explode(',', $currentRequest->query->get('expand', []));
return $this->createWithGroups([]);
}
/**
* @param array $groups
*
* @return SerializationContext
*/
public function createWithGroups(array $groups): SerializationContext
{
$serializationContext = SerializationContext::create();
$serializationContext->setGroups(array_merge(['Default'], $groups));
return $serializationContext;
}
}
nah sekarang kita coba akses menggunakan POSTMAN :
untuk menggunakan paging, bisa diakses dengan url seperti berikut:
untuk paging saya kira cukup sekian dulu, sekarang kita lanjut untuk insert dan update serta delete data.
Insert
untuk melakukan input dan edit data, kali ini saya menggunakan form untuk menerima dan melakukan proses data yang di inputoleh user. Untuk membuat form anda bisa menjalankan perintah berikut pada CMD / console di folder project anda.
php bin/console make:formseletah selesai maka kita akan punya form baru di dalam folder src/Form. setelah selesai membuat form, fungsi insert data kita ubah function insert() yang ada di controller ApiController.php menjadi seperti berikut:
// src/Controller/ApiController.php
/**
* @Route("/api/insert", name="api_category_insert", methods={"GET","POST"})
*/
public function insert(Request $request, ValidatorInterface $validator)
{
$MCategory = new MCategory(); // membuat object MCategory
if($request->getMethod() == Request::METHOD_POST) { // jalankan jika method POST
// membuat object form untuk menangkap inputan dari user
$form = $this->createForm(MCategoryType::class, $MCategory);
// memanggil function proccessForm yang ada di BaseController
//untuk memproses data input dari user
$MCategory = $this->processForm($MCategory, $form, $request);
// check apakah data yang dikirim valid / tidak
$valid = $MCategory instanceof MCategory;
if(!$valid){ // kirimkan pesan error jika data yang dikirim tidak valid
return $this->getResponseSuccess($MCategory);
}
$result =["status" => true, "messages" => 'data saved successfully'];
return $this->getResponseSuccess($MCategory);
}
// membuat data form untuk ditampilkan ke user
$form = $this->createFormApi(MCategoryType::class, $MCategory);
return $this->getResponseSuccess($form);
}
kode diatas memanggl beberapa fungsi yang berada di class BaseController seperti $this->processForm(), $this->getResponseSuccess() dan $this->createFormApi(). Sekarang kita buka class BaseController dan ubah beberapa function tersebut:
- processForm
// src/Controller/Base/BaseController.php
protected function processForm($obj, Form $form, Request $request)
{
$entityManager = $this->getDoctrine()->getManager();
$con = $entityManager->getConnection();
try{
$con->beginTransaction();
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$obj = $form->getData();
$entityManager->persist($obj);
$entityManager->flush();
$con->commit();
}else{
return $this->getErrors($form);
}
} catch (Exception $ex) {
$con->rollback();
return array("status" => false, "messages" => 'error : '.$ex->getMessage());
}
return $obj;
}
- getErrors
// src/Controller/Base/BaseController.php
protected function getErrors($form) {
$errors = array();
foreach ($form as $k => $child) {
foreach($child->getErrors() as $key => $error){
$errors[$k][$key] = $error->getMessageTemplate();
}
if(isset($errors[$k])){
$errors[$k] = implode(", ", $errors[$k]);
}
}
if(!empty($errors)){
return array("status" => false, "errors" => $errors);
}else{
return array(
"status" => false,
"errors" => 'key name must :'. $form->getName());
}
return $errors;
}
- createFormApi
// src/Controller/Base/BaseController.php
/**
* @param type Form
*/
public function createFormApi(string $type, $data = null, array $options = array())
{
$form = parent::createForm($type, $data, $options);
$data = [];
foreach($form as $k => $v) {
$vars = $v->createView()->vars;
unset($vars['form']);
unset($vars['block_prefixes']);
$data[$k] = $vars;
}
return $data;
}
sampai disini sekarang coba kita panggil menggunakan POSTMAN seperti berikut:
- GET
-POST (dengan nilai code = null)
-POST (dengan input secara benar)
sampai disini kita telah selesai membuat halaman insert data.
Update
sekarang kita lanjut untuk fungsi ubah data. Sekarang silahkan ubah function update menjadi seperti berikut ini:
kode di atas sudah saya sertakan penjelasan di komentar kodenya.
// src/Controller/ApiController.php
/**
* @Route("/api/update/{id}", name="api_category_update", methods={"GET","POST"})
*/
public function update(MCategory $MCategory, Request $request)
{
if($request->getMethod() == Request::METHOD_POST) { // jalankan jika method == POST
$form = $this->createForm(MCategoryType::class, $MCategory); // membuat object form untuk melakukan proses data
$MCategory = $this->processForm($MCategory, $form, $request); // memanggil funcgsi processForm di BaseController untuk memproses data
$valid = $MCategory instanceof MCategory;
if(!$valid){
return $this->getResponseSuccess($MCategory); // tampilkan pesan error jika ada error
}
$result =["status" => true, "messages" => 'data saved successfully'];
return $this->getResponseSuccess($MCategory);
}
$form = $this->createFormApi(MCategoryType::class, $MCategory);
return $this->getResponseSuccess($form);
}
kode di atas sudah saya sertakan penjelasan di komentar kodenya.
Delete
untuk hapus data, silahkan ubah function delete menjadi seperti berikut:
sehingga untuk keseluruhan kode ApiController.php menjadi seperti berikut:
cukup sekian tulisan saya kali ini, terima kasih semoga bermanfaat.
Untuk tulisan saya selanjutnya, bisa dibaca di link berikut :
https://catatan-pemrograman.blogspot.com/2018/10/menggunakan-translation-di-symfony-4.html
yang membahas tentang Translations.
// src/Controller/ApiController.php
/**
* @Route("/api/delete/{id}", name="api_category_delete", methods={"DELETE"})
*/
public function deleted(MCategory $MCategory)
{
// menghapus data
$entityManager = $this->getDoctrine()->getManager();
$entityManager->remove($MCategory);
$entityManager->flush();
$data = ['status' => true, 'messages' => 'data berhasil dihapus'];
return $this->getResponseSuccess($data);
}
sehingga untuk keseluruhan kode ApiController.php menjadi seperti berikut:
<?php // src/Controller/ApiController.php
namespace App\Controller;
use App\Entity\MCategory;
use App\Controller\Base\BaseController;
use App\Form\MCategoryType;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class ApiController extends BaseController
{
/**
* @Route("/api", name="api")
*/
public function index(Request $request)
{
$queryBuilder = $this->getQueryBuilder(MCategory::class);
$paginator = $this->createPaginator($request, $queryBuilder->getQuery());
return $this->getResponseSuccess($this->getPaginationData($paginator));
}
/**
* @Route("/api/insert", name="api_category_insert", methods={"GET","POST"})
*/
public function insert(Request $request, ValidatorInterface $validator)
{
$MCategory = new MCategory(); // membuat object MCategory
if($request->getMethod() == Request::METHOD_POST) { // jalankan jika method POST
$form = $this->createForm(MCategoryType::class, $MCategory); // membuat object form untuk menangkap inputan dari user
$MCategory = $this->processForm($MCategory, $form, $request); // memanggil function proccessForm yang ada di BaseController untuk memproses data input dari user
$valid = $MCategory instanceof MCategory; // check apakah data yang dikirim valid / tidak
if(!$valid){ // kirimkan pesan error jika data yang dikirim tidak valid
return $this->getResponseSuccess($MCategory);
}
$result =["status" => true, "messages" => 'data saved successfully'];
return $this->getResponseSuccess($MCategory);
}
$form = $this->createFormApi(MCategoryType::class, $MCategory); // membuat data form untuk ditampilkan ke user
return $this->getResponseSuccess($form);
}
/**
* @Route("/api/update/{id}", name="api_category_update", methods={"GET","POST"})
*/
public function update(MCategory $MCategory, Request $request)
{
if($request->getMethod() == Request::METHOD_POST) { // jalankan jika method == POST
$form = $this->createForm(MCategoryType::class, $MCategory); // membuat object form untuk melakukan proses data
$MCategory = $this->processForm($MCategory, $form, $request); // memanggil funcgsi processForm di BaseController untuk memproses data
$valid = $MCategory instanceof MCategory;
if(!$valid){
return $this->getResponseSuccess($MCategory); // tampilkan pesan error jika ada error
}
$result =["status" => true, "messages" => 'data saved successfully'];
return $this->getResponseSuccess($MCategory);
}
$form = $this->createFormApi(MCategoryType::class, $MCategory);
return $this->getResponseSuccess($form);
}
/**
* @Route("/api/delete/{id}", name="api_category_delete", methods={"DELETE"})
*/
public function deleted(MCategory $MCategory)
{
// menghapus data
$entityManager = $this->getDoctrine()->getManager();
$entityManager->remove($MCategory);
$entityManager->flush();
$data = ['status' => true, 'messages' => 'data berhasil dihapus'];
return $this->getResponseSuccess($data);
}
}
cukup sekian tulisan saya kali ini, terima kasih semoga bermanfaat.
Untuk tulisan saya selanjutnya, bisa dibaca di link berikut :
https://catatan-pemrograman.blogspot.com/2018/10/menggunakan-translation-di-symfony-4.html
yang membahas tentang Translations.
0 Komentar