Selamat Datang di blog sederhana saya,Semoga apa yang Kalian Inginkan dapat terpenuhi disini.

Menambahkan Translator di URL Symfony 4

melanjutkan tulisan saya sebelumnya tentang Translation di symfony 4, kali ini saya lanjutkan dengan translation di routing symfony. Contoh penggunaan translation di url adalah seperti url berikut : 


nah kira kira kita akan belajar membuat translation di url. 

Event

pertama kita buat folder baru bernama EventSubscriber di dalam folder src. Oh iya, bagi yang belum punya bahan untuk latihan kali ini, silahkan download di akun github saya di https://github.com/kematjaya0/tutorial atau silahkan baca ditulisan saya sebelumnya di https://catatan-pemrograman.blogspot.com/2018/10/menggunakan-translation-di-symfony-4.html

Kemudian buat file baru bernama LocaleRewriteListener,php yang implements class EventSubscriberInterface.php. Seperti pada kode berikut : 
<?php //src/EventSubscriber/LocaleRewriteListener.php

/**
* Description of LocaleRewriteListener
*
* @author NUR HIDAYAT
*/

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class LocaleRewriteListener implements EventSubscriberInterface{

/**
* @var Symfony\Component\Routing\RouterInterface
*/
private $router;

/**
* @var routeCollection \Symfony\Component\Routing\RouteCollection
*/
private $routeCollection;

/**
* @var string
*/
private $defaultLocale;

/**
* @var array
*/
private $supportedLocales;

/**
* @var string
*/
private $localeRouteParam;

public function __construct(RouterInterface $router, $defaultLocale = 'id', array $supportedLocales = array(), $localeRouteParam = '_locale')
{
$this->router = $router;
$this->routeCollection = $router->getRouteCollection();
$this->defaultLocale = $defaultLocale;
$this->supportedLocales = $supportedLocales;
$this->localeRouteParam = $localeRouteParam;
}

public function isLocaleSupported($locale)
{
return in_array($locale, $this->supportedLocales);
}

public function onKernelRequest(GetResponseEvent $event) // function yang wajib ada di setiap class yang implements dari interface EventSubscriberInterface
{
$request = $event->getRequest(); // mengambil object Request
$path = $request->getPathInfo(); // mengambil path / url dari request

$route_exists = false; //by default assume route does not exist.

// check dari routing yang tersedia, apakah url ada atau tidak
foreach($this->routeCollection as $routeObject){
$routePath = $routeObject->getPath();
if($routePath == "/{_locale}".$path){
$route_exists = true;
break;
}
}

// jika path routing ada
if($route_exists == true){
$locale = $request->getPreferredLanguage(); // ambil bahasa

if($locale=="" || $this->isLocaleSupported($locale)==false){
$locale = $request->getDefaultLocale();
}

// set url dengan disertai bahasa
$event->setResponse(new RedirectResponse($request->getSchemeAndHttpHost().$request->getBaseUrl()."/".$locale.$path));
}
}

public static function getSubscribedEvents() // override function untuk setting prioritas class yang dijalankan terlebih dahulu
{
return array(
KernelEvents::REQUEST => array(array('onKernelRequest', 19)),
);
}
}

class di atas adalah Event Dispatcher yang digunakan untuk meredirect url / mereplace url yg masuk yang apabila url request tidak disertai dengan bahasa / locale, maka akan ditambahkan dengan bahasa / locale default dari aplikasi. Untuk pembahasan Event Dispatcher bisa disimak pada tulisan saya lain kali. Setiap class yang implements interface EventSubscriberInterface.php harus mempunyai function onKernelRequest() yang akan dijalankan saat terjadi request ke aplikasi. Kemudian tambahkan / daftarkan file Event Dispatcher kita di config service.yml.
    App\EventSubscriber\LocaleRewriteListener:
arguments: ["@router", "%kernel.default_locale%", "%locale_supported%"]
tags:
- { name: kernel.event_subscriber }

dan tambahkan kode berikut di bagian parameters sehingga menjadi seperti berikut:
parameters:
locale: 'en'
    app_locales: en|id
locale_supported: ['en','id']

sehingga file service.yml kita menjadi seperti berikut :
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: 'en'
    app_locales: en|id
locale_supported: ['en','id']

services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.

# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']

App\EventSubscriber\LocaleRewriteListener:
arguments: ["@router", "%kernel.default_locale%", "%locale_supported%"]
tags:
- { name: kernel.event_subscriber }
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

kemudian buat lagi satu class bernama LocaleSubscriber.php di dalam folder src/EventSubscriber yang juga implement dari Interface Symfony\Component\EventDispatcher\EventSubscriberInterface.php seperti berikut:
<?php // src/EventSubscriber/LocaleSubscriber.php

/**
* Description of LocaleSubscriber
*
* @author NUR HIDAYAT
*/

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class LocaleSubscriber implements EventSubscriberInterface{

private $defaultLocale;

public function __construct($defaultLocale = 'id')
{
$this->defaultLocale = $defaultLocale;
}


public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}

if ($locale = $request->attributes->get('_locale')) {
$request->getSession()->set('_locale', $locale);
}else{
$request->getSession()->set('_locale', $this->defaultLocale);
}
$request->setDefaultLocale($request->getSession()->get('_locale', $this->defaultLocale));
}

public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => array(array('onKernelRequest', 21)),
);
}
}

class diatas digunakan untuk menyimpan settingan bahasa yang diperoleh dari url dan disimpan ke session agar dapat digunakan ditempat lain seperti di Controller atau di template twig. Kemudian jangan lupa untuk menambahkannya di file services.yaml sehingga file config/services.yaml kita menjadi seperti berikut:
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: 'en'
    app_locales: en|id
locale_supported: ['en','id']

services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.

# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']

App\EventSubscriber\LocaleSubscriber:
arguments: ['%kernel.default_locale%']

App\EventSubscriber\LocaleRewriteListener:
arguments: ["@router", "%kernel.default_locale%", "%locale_supported%"]
tags:
- { name: kernel.event_subscriber }
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

nah kemudian untuk memeriksa apakah file kita berhasil di load / dijalankan, buka folder project kita di cmd kemudian jalankan perintah berikut:

php bin/console debug:event-dispatcher kernel.request

maka kira-kira akan muncul seperti ini:

nah, jika class tadi muncul di daftar tersebut seperti gambar di atas, maka kita berhasil menambahkannya sebagai event di symfony.

Routing

Sampai disini, kita sudah berhasil menambahkan Event di project symfony kita kemudian kita lanjutkan untuk konfigurasi routing. Kita buka file config/routes/annotations.yaml sehingga menjadi seperti berikut : 
controllers:
resource: ../../src/Controller/
type: annotation
prefix: /{_locale}
requirements:
_locale: '%app_locales%'
defaults:
_locale: '%locale%'

ini untuk memaksa pengguna / user untuk memasukkan kode negara di url. Untuk memeriksa daftar routing, jalankan perintah ini di CMD : 

php bin/console debug:router

sehigga muncul daftar seperti gambar berikut:

nah pada gambar di atas terlihat bahwa routing kita menjadi /{_locale}/ yang berarti user harus menyertakan kode negara untuk mengakses halaman web. Sekarang kita coba akses menggunakan POSTMAN:
pada contoh di atas, saya mencoba melakukan insert data dengan url disertai kode negara inggris : 

http://localhost:8000/en/api/test/insert

sehingga pesan yang keluar adalah bahasa inggris, sekarang kita coba POST data menggunakan url dengan kode id :

http://localhost:8000/id/api/test/insert

sehingga pesan yang keluar adalah bahasa indonesia seperti pada gambar berikut:

sekian dulu tulisan saya kali ini, semoga bermanfaat, dan lain kesempatan saya akan coba tulis tentang Service. Terima kasih

Catatan : Sorce Code Latihan dapat di unduh di github saya di https://github.com/kematjaya0/tutorial
Share:

Menggunakan Translation di Symfony 4

Selamat pagi, siang, malam untuk anda yang kebetulan sedang mampir di blog saya, melanjutkan seri tulisan saya tentang symfony 4, kali ini saya akan menulis tentang Translation. Apa itu Translation ? yah... simpelnya sejenis tool untuk membuat aplikasi kita bisa multi bahasa. Untuk tulisan saya sebelumnya bisa dibaca di https://catatan-pemrograman.blogspot.com/2018/10/membuat-api-crud-di-symfony-4.html. Da untuk bahan praktek saya, bisa diunduh di akun github saya disini : https://github.com/kematjaya0/tutorial. Oke cekidot....

Instalasi Translation

tulisan ini saya praktekkan dan saya rangkum dari halaman resmi symfony di https://symfony.com/doc/current/components/translation.html. Langsung saja untuk instalasi silahkan masuk ke direktori / folder project latihan kita dengan menggunakan CMD. Kalau saya di

F:\xampp_72\htdocs\tutorial

kemudian jalankan perintah berikut: 
composer require symfony/translation

dan ikuti instruksi yang diberikan. Setelah selesai instalasi, silahkan buka file config di config/packages/translation.yaml
framework:
default_locale: '%locale%'
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- '%locale%'

dari kode diatas, folder untuk membuat file translation adalah di folder translations. Untuk simulasi dual bahasa, disini saya coba buat antara bahasa Indonesia dengan bahasa Inggris. Untuk memulainya kita buat file translator di dalam folder translations. masing masing messages.en.yml untuk bahasa Inggris dan file messages.id.yml untuk bahasa Indonesia, seperti berikut,

// translations/messages.en.yml
test: Hai, this is symfony framework

// translations/messages.id.yml
test: Hai, ini latihan symfony framework

pada contoh file diatas, yg sebelah kiri disebut key dan yang kanan adalah value. Kemudan buka file config/packages/framework.yaml dan tambahkan kode berikut :
framework:
translator: { fallback: '%locale%' }
secret: '%env(APP_SECRET)%'

kemudian buka file config/services.yaml. Kemudian lihat di bagian parameter berikut :
parameters:
locale: 'id'

Pada potongan kode di atas, default bahasa / locale yang digunakan adalah id / Indonesia. Untuk menggunakan bahasa inggris, anda bisa mengganti value dari parameter locale dengan en. Untuk penggunaan di controller, sekarang kita buka file src/Controller/ApiController.php: 
<?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;
/**
* @Route("/api/test")
*/
class ApiController extends BaseController
{
/**
* @Route("/", name="api_index")
*/
public function index(Request $request)
{
$queryBuilder = $this->getQueryBuilder(MCategory::class);
$paginator = $this->createPaginator($request, $queryBuilder->getQuery());
return $this->getPaginationData($paginator);
}

/**
* @Route("/insert", name="api_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 $form;
}

/**
* @Route("/update/{id}", name="api_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("/delete/{id}", name="api_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);
}
}

sebagai contoh kita akan ubah di function index, pertama tambahkan kode berikut untuk menggunakan translator:
<?php // src/Controller/ApiController.php

namespace App\Controller;

....
use Symfony\Component\Translation\TranslatorInterface;

kemudian ubah function index menjadi seperti berikut:
<?php //src/Controller/ApiController.php

namespace App\Controller;

...
use Symfony\Component\Translation\TranslatorInterface;
/**
* @Route("/api/test")
*/
class ApiController extends BaseController
{
/**
* @Route("/", name="api_index")
*/
public function index(Request $request, TranslatorInterface $translator)
{
return $translator->trans('test');
}

}

kemudian panggil menggunakan POSTMAN / di browser, sehingga hasilnya sebagai berikut,

kemudian untuk mencoba bahasa inggris saya coba ganti parameter locale yang ada di file config/services.yaml menjadi en seperti berikut:
parameters:
locale: 'en'

maka setelah saya coba lagi di browser / POSTMAN, maka hasilnya sebagai berikut:

mudah bukan ? sekarang kita perbaiki function kita yang lain agar bisa multi bahasa. Pertama kita buka file translation kita dan tambahkan menjadi seperti berikut :

// translations/messages.en.yml
test: Hai, this is symfony framework
save_success: Data saved successfully
update_success: Data updated successfully
delete_success: Data deleted successfully

// translations/messages.id.yml
test: Hai, ini latihan symfony framework
save_success: Data berhasil disimpan
update_success: Data berhasil diubah
delete_success: Data berhasil dihapus

kemudian kita ubah controller ApiController.php menjadi seperti berikut :

// function index, untuk index kita kembalikan seperti semula karena dalam contoh ini di dalam function index tidak ada label yang perlu di translate ke dalam multi bahasa

     /**
* @Route("/", name="api_index")
*/
public function index(Request $request)
{
$queryBuilder = $this->getQueryBuilder(MCategory::class);
$paginator = $this->createPaginator($request, $queryBuilder->getQuery());
return $this->getPaginationData($paginator);
}

// function insert
     /**
* @Route("/insert", name="api_insert", methods={"GET","POST"})
*/
public function insert(Request $request, ValidatorInterface $validator, TranslatorInterface $translator)
{
$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" => $translator->trans('save_success')];
return $this->getResponseSuccess($MCategory);
}
$form = $this->createFormApi(MCategoryType::class, $MCategory); // membuat data form untuk ditampilkan ke user
return $form;
}

// function update
     /**
* @Route("/update/{id}", name="api_update", methods={"GET","POST"})
*/
public function update(MCategory $MCategory, Request $request, TranslatorInterface $translator)
{
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" => $translator->trans('update_success')];
return $this->getResponseSuccess($MCategory);
}
$form = $this->createFormApi(MCategoryType::class, $MCategory);
return $this->getResponseSuccess($form);
}

// function deleted
     /**
* @Route("/delete/{id}", name="api_delete", methods={"DELETE"})
*/
public function deleted(MCategory $MCategory, TranslatorInterface $translator)
{
// menghapus data
$entityManager = $this->getDoctrine()->getManager();
$entityManager->remove($MCategory);
$entityManager->flush();
$data = ['status' => true, 'messages' => $translator->trans('delete_success')];
return $this->getResponseSuccess($data);
}

kemudian silahkan dicoba pakai POSTMAN. Mudah bukan ?

Sementara cukup sekian dulu tulisan saya kali ini, semoga bermanfaat. Untuk mengunduh project latihan ini anda bisa kunjungi akun github saya di https://github.com/kematjaya0/tutorial.

Untuk tulisan saya selanjutnya, insya allah saya lanjutkan untuk memasukkan translation pada url di browser. Sampai Jumpa...
Share:

Membuat API CRUD di Symfony 4 ( FOSRestBundle )

      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/form
untuk 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)
{

}
}
sekarang kita lihat function index di atas, pada function index terdapat beberapa kode yang memanggil function yg berada di class BaseController seperti $this->getQueryBuilder(), $this->CreatePaginator() dan $this->getResponseSuccess(). Sekarang kita buka kembali file BaseController.php dan ubah function tersebut menjadi seperti berikut:

- 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:form
seletah 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:

    // 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:

    // 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.
Share:
  • ()
  • ()
Show more

Halaman