Tulisan ini merupakan lanjutan dari tulisan saya sebelumnya mengenai symfony 4, kali ini saya lanjutkan menulis tentang Service Container yang kalau gampangnya menurut bahasa saya adalah, kita bisa membuat class kita sendiri dengan berbagai fungsi untuk membantu pekerjaan kita yg bisa di akses di berbagai tempat seperti Controller, Event, atau di akses dari Service yang lain. Contoh beberapa Service bawaan Symfony seperti Mail, Logger, dll. Nah kali ini saya mau coba tulis cara bagaimana kita bisa membuat Service kita sendiri di Symfony 4.

Notes : 
Untuk project latihan, silahkan download sorce code latihan dari seri tulisan symfony ini di akun github saya di https://github.com/kematjaya0/tutorial

dan untuk seri tulisan saya sebelumnya bisa dibaca di : 

Artikel resimi mengenai Service Container bsia dilihat di halaman berikut https://symfony.com/doc/current/service_container.html, oke sekarang kita lanjutkan membuat Service.

Membuat Service

Pada tulisan saya kali ini, saya akan coba membuat service yang bertugas untuk melakukan pencatatan log. Sebagai contoh, saya membuat Controller baru HelloController.php di dalam folder src/Controller:
<?php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* Description of HelloController
*
* @author NUR HIDAYAT
*/
class HelloController {

/**
*
* @Route("/hello", name="hello")
*/
public function index()
{
$number = random_int(0, 100);
return new Response(
'<html><body>Lucky number: '.$number.'</body></html>'
);
}
}
kode diatas merupakan controller sederhana yang akan menghasilkan halaman html berisi angka random / acak antara 0 sampai 100. Kemudian untuk menjalankan, jalankan perintah berikut melalui CMD di dalam folder project symfony kita,
php bin/console server:run
kemudian akses di browser dengan url berikut:
http://localhost:8000/hello
Selanjutnya kita gunakan Service Logger untuk mencatat ip address yang sedang mengakses halaman tersebut , ubah controller sehingga menjadi seperti berikut :
<?php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request; // tambahkan class Request
use Psr\Log\LoggerInterface; // tambahkan class LoggerInterface
/**
* Description of HelloController
*
* @author NUR HIDAYAT
*/
class HelloController {

/**
*
* @Route("/hello", name="hello")
*/
public function index(LoggerInterface $logger, Request $request) // Inject class LoggerInterface dan Request sebagai parameter
{
$number = random_int(0, 100);
// fungsi untuk menampilkan log
$logger->info(date('d M Y H:i:s').' : '.$request->getClientIp().' access path => '.$request->getRequestUri());
return new Response(
'<html><body>Lucky number: '.$number.'</body></html>'
);
}
}
pada kode di atas sudah saya tandai dan juga beri keterangan, kemudian akses kembali halaman di browser dan periksa di CMD kita yang tadi menjalankan server:
pada gambar di atas, bisa dilihat bahwa aplikasi menampilkan pesan ke CMD. Kode di atas adalah contoh penggunaan Service Container bawaan symfony. Sekarang kita coba buat Service Container sendiri, Cekidot.

Membuat Container Sendiri

Pada tulisan saya kali ini, saya ingin membuat Service Container berupa Class Calculator.php yang berisi fungsi-fungsi perhitungan aritmatika sederhana seperti perkalian, pembagian, penjumlahan dll. Pertama kita buat folder Service di dalam folder src. Kemudian buat class Calculator.php di dalam folder Service seperti berikut : 
<?php // src/Service/Calculator.php

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

namespace App\Service;

class Calculator {

public function tambah($a, $b)
{
return $a + $b;
}

public function kurang($a, $b)
{
return $a - $b;
}

public function kali($a, $b)
{
return $a * $b;
}

public function bagi($a, $b)
{
return $a/$b;
}

public function percen($percen, $nilai)
{
return $nilai * $percen / 100;
}
}
kemudian ubah controller HelloController.php sehingga menjadi seperti berikut:
<?php //src/Controller/HelloController.php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Psr\Log\LoggerInterface;
use App\Service\Calculator; // tambahkan class Calculator
/**
* Description of HelloController
*
* @author NUR HIDAYAT
*/
class HelloController {

/**
*
* @Route("/hello", name="hello")
*/
public function index(LoggerInterface $logger, Request $request, Calculator $calculator) // Inject class Calculator sebagai parameter
{
$a = 10; // variabel pertama yang akan dihitung
$b = 100;// variabel kedua yang akan dihitung
$number = $calculator->tambah($a, $b); // memanggil fungsi tambah
$logger->info(date('d M Y H:i:s').' : '.$request->getClientIp().' access path => '.$request->getRequestUri());
return new Response(
'<html><body>Lucky '.$a.' + '.$b.' : '.$number.'</body></html>'
);
}
}
pada kode di atas sudah saya sertai penjelasan baris kode yang ditambahkan. Kode di atas saya memanggil fungsi tambah untuk melakukan penjumlahan dua buat variabel. Kemudian jalankan kembali di browser sehingga jika jalan hasilnya akan seperti berikut:
untuk melihat daftar Service Container yang ada, kita bisa menjalankan kode berikut pada CMD:
php bin/console debug:autowiring
kurang lebih akan muncul seperti berikut.

Menggunakan Service Container pada Service

Kita bisa menggunakan Service Container lain bawaan symfony atau buatan yang lain pada Service buatan kita dengan cara melakukan Dependency Injection pada class buatan kita. Sebagai contoh, saya akan memasukkan class LoggerInterface pada class Calculator yang baru saja kita buat. Ubah class Calculator.php sehingga menjadi seperti berikut.
<?php // src/Service/Calculator.php

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

namespace App\Service;

use Psr\Log\LoggerInterface; // tambahkan class LoggerInterface

class Calculator {

private $logger; // buat variabel $logger

public function __construct(LoggerInterface $logger) { // buat fungsi __construct dengan memasukkan class LoggerInterface sebagai parameter
$this->logger = $logger;
}
public function tambah($a, $b)
{
$this->logger->info('tambah : '.$a.' + '.$b.' = '. ($a + $b)); // menggunakan logger
return $a + $b;
}

public function kurang($a, $b)
{
$this->logger->info('kurang : '.$a.' - '.$b.' = '. ($a - $b)); // menggunakan logger
return $a - $b;
}

public function kali($a, $b)
{
$this->logger->info('kali : '.$a.' * '.$b.' = '. ($a * $b)); // menggunakan logger
return $a * $b;
}

public function bagi($a, $b)
{
$this->logger->info('bagi : '.$a.' / '.$b.' = '. ($a / $b)); // menggunakan logger
return $a/$b;
}

public function percen($percen, $nilai)
{
return $nilai * $percen / 100;
}
}
pada kode di atas, saya memasukkan class LoggerInterface sebagai parameter function __construct(), kemudian saya memanggilnya saat function tambah(), kurang(), bagi(), kali(). Tehnik ini dinamakan Dependency Injection yang bisa anda pelajari lebih lanjut pada halaman berikut https://phptherightway.com/#dependency_injection

Menggunakan Alias

Pada tulisan saya di atas, saya mencoba membuat dan memanggil Service Container yang kita buat dengan cara Dependency Injection. Untuk memanggil, kita bisa juga menggunakan alias sehingga kita tidak perlu menambahkan Class Calculator yang kita buat di bagian atas Controller. Untuk membuat alias, kita buka file services.yaml yang ada di folder config. Buka dan tambahkan kode berikut di dalam tag services : 
    # membuat alias untuk class Calculator
app.calculator:
public: true
class: App\Service\Calculator
arguments: ['@logger']
# ------- end alias class Calculator ----
sehingga file services.yaml menjadi seperti berikut: 
# config/services.yaml

# 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']

# membuat alias untuk class Calculator
app.calculator:
public: true
class: App\Service\Calculator
arguments: ['@logger']
# ------- end alias class Calculator --------
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
kemudian ubah controller sehingga menjadi seperti berikut: 
<?php //src/Controller/HelloController.php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; // tambahkan class Controller
/**
* Description of HelloController
*
* @author NUR HIDAYAT
*/
class HelloController extends Controller{ // class diubah sehingga menjadi extends Controller

/**
*
* @Route("/hello", name="hello")
*/
public function index(LoggerInterface $logger, Request $request)
{
$a = 10; // variabel pertama yang akan dihitung
$b = 100;// variabel kedua yang akan dihitung
$calculator = $this->get('app.calculator'); // memanggil service calculator sesuai nama di service.yml
$number = $calculator->tambah($a, $b); // memanggil fungsi tambah
$logger->info(date('d M Y H:i:s').' : '.$request->getClientIp().' access path => '.$request->getRequestUri());
return new Response(
'<html><body>Lucky '.$a.' + '.$b.' : '.$number.'</body></html>'
);
}
}
pada kode di atas, saya melakukan beberapa perubahan, antara lain Class HelloController saya ubah sehingga menjadi extends ke Class Controller sehingga bisa memanggil Service Container menggunakan nama id / alias yang kita tulis di file services.yaml.

Kesimpulan

Dengan menggunakan Service Container, Controller kita bisa lebih bersih karena kita memisahkan fungsi-fungsi yang bisa dipakai ulang ke dalam Service Container. Kita bisa membuat beberapa fungsi seperti kirim email, upload file, atau yang lain menjadi sebuah Service sehingga bisa kita pakai ulang pada modul / Controller yang lain.

Terima kasih sudah membaca tulisan saya kali ini, semoga bermanfaat