Symfony - Framework PHP Enterprise
Dipublikasikan pada 15 Januari 2024 โข โฑ๏ธ 50 menit baca
Apa itu Symfony?
Symfony adalah framework PHP yang matang dan robust, didesain untuk pengembangan aplikasi web enterprise. Symfony mengutamakan best practices, reusability, dan maintainability.
Keunggulan Symfony
๐๏ธ Architecture yang Solid
Menggunakan design patterns yang proven dan arsitektur yang terstruktur.
๐ง Dependency Injection
Service container yang powerful untuk dependency management.
๐ฆ Reusable Components
Components dapat digunakan di project lain atau framework lain.
๐ High Performance
Optimized untuk performa tinggi dengan caching yang baik.
๐งช Testing
Built-in testing tools untuk unit dan functional testing.
๐ Rich Ecosystem
Bundles dan packages yang sangat lengkap.
Symfony Components
Symfony terdiri dari lebih dari 30 standalone components:
- HttpFoundation - HTTP abstraction
- Routing - URL routing
- Console - Command line applications
- EventDispatcher - Event system
- Validator - Data validation
- Security - Authentication & authorization
- Serializer - Object serialization
- Form - Form handling
- Translation - Internationalization
- Cache - Caching abstraction
Kapan Menggunakan Symfony?
- Aplikasi enterprise dengan kompleksitas tinggi
- Tim development yang besar
- Project yang membutuhkan maintainability jangka panjang
- Aplikasi dengan requirements performa tinggi
- API yang kompleks dan scalable
Instalasi Symfony
Prasyarat Sistem
- PHP >= 8.1
- Composer
- Symfony CLI (opsional tapi direkomendasikan)
- Database (MySQL/PostgreSQL)
1. Install Symfony CLI
# Windows (dengan Scoop)
scoop install symfony-cli
# macOS (dengan Homebrew)
brew install symfony-cli/tap/symfony-cli
# Linux
curl -sS https://get.symfony.com/cli/installer | bash
2. Buat Project Symfony
# Menggunakan Symfony CLI (direkomendasikan)
symfony new my-symfony-app --version=6.3 --webapp
# Atau menggunakan Composer
composer create-project symfony/skeleton my-symfony-app
cd my-symfony-app
composer require webapp
# Masuk ke direktori project
cd my-symfony-app
3. Menjalankan Development Server
# Menggunakan Symfony CLI
symfony serve
# Atau menggunakan PHP built-in server
php -S localhost:8000 -t public/
# Akses di browser: https://localhost:8000
4. Environment Configuration
Edit file .env
:
# Database configuration
DATABASE_URL="mysql://root:@127.0.0.1:3306/symfony_db?serverVersion=8.0.32&charset=utf8mb4"
# App environment
APP_ENV=dev
APP_SECRET=your-secret-key-here
# Mailer configuration
MAILER_DSN=smtp://localhost:1025
5. Install Dependencies Tambahan
# Doctrine ORM
composer require symfony/orm-pack
# Symfony Maker Bundle (untuk code generation)
composer require --dev symfony/maker-bundle
# Twig template engine
composer require twig
# Security bundle
composer require symfony/security-bundle
# Form handling
composer require symfony/form
Struktur Direktori Symfony
symfony-project/
โโโ bin/ # Executable files
โ โโโ console # Symfony console
โโโ config/ # Configuration files
โ โโโ packages/ # Package configurations
โ โโโ routes/ # Routing configurations
โ โโโ bundles.php # Bundle registration
โ โโโ services.yaml # Service definitions
โโโ migrations/ # Database migrations
โโโ public/ # Public assets
โ โโโ index.php # Front controller
โโโ src/ # Application source code
โ โโโ Controller/ # Controllers
โ โโโ Entity/ # Doctrine entities
โ โโโ Repository/ # Doctrine repositories
โ โโโ Form/ # Form types
โ โโโ Security/ # Security classes
โ โโโ Service/ # Business logic services
โ โโโ Kernel.php # Application kernel
โโโ templates/ # Twig templates
โโโ tests/ # Tests
โโโ translations/ # Translation files
โโโ var/ # Cache, logs, sessions
โ โโโ cache/ # Application cache
โ โโโ log/ # Log files
โโโ vendor/ # Composer dependencies
โโโ .env # Environment variables
โโโ composer.json # Composer configuration
โโโ symfony.lock # Symfony Flex lock file
Direktori Penting
- src/ - Semua kode aplikasi Anda
- config/ - Konfigurasi aplikasi dan services
- templates/ - Twig templates untuk views
- public/ - Web-accessible files
- var/ - Cache dan log files
Symfony Flex
Symfony Flex adalah tool untuk mengelola Symfony applications dengan recipes:
# Install package dengan recipe
composer require logger
# List available recipes
composer recipes
# Update recipes
composer recipes:update
Controllers di Symfony
1. Basic Controller
# Generate controller
php bin/console make:controller HomeController
File: src/Controller/HomeController.php
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class HomeController extends AbstractController
{
#[Route('/', name: 'app_home')]
public function index(): Response
{
return $this->render('home/index.html.twig', [
'title' => 'Welcome to Symfony',
'message' => 'Hello World!'
]);
}
#[Route('/about', name: 'app_about')]
public function about(): Response
{
return $this->render('home/about.html.twig');
}
#[Route('/user/{id}', name: 'app_user', requirements: ['id' => '\d+'])]
public function user(int $id): Response
{
return new Response("User ID: " . $id);
}
#[Route('/api/data', name: 'api_data', methods: ['GET'])]
public function apiData(Request $request): Response
{
$data = [
'message' => 'API endpoint',
'timestamp' => time(),
'params' => $request->query->all()
];
return $this->json($data);
}
}
?>
2. Request dan Response
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Annotation\Route;
class RequestController extends AbstractController
{
#[Route('/form', name: 'form_page')]
public function form(Request $request): Response
{
if ($request->isMethod('POST')) {
$name = $request->request->get('name');
$email = $request->request->get('email');
// Process form data
$this->addFlash('success', 'Form submitted successfully!');
return $this->redirectToRoute('form_page');
}
return $this->render('form.html.twig');
}
#[Route('/api/users', name: 'api_users', methods: ['POST'])]
public function createUser(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true);
// Validate data
if (!isset($data['name']) || !isset($data['email'])) {
return $this->json(['error' => 'Name and email are required'], 400);
}
// Create user logic here
return $this->json([
'id' => 123,
'name' => $data['name'],
'email' => $data['email'],
'created_at' => date('Y-m-d H:i:s')
], 201);
}
#[Route('/download/{filename}', name: 'download_file')]
public function downloadFile(string $filename): Response
{
$filePath = $this->getParameter('kernel.project_dir') . '/var/files/' . $filename;
if (!file_exists($filePath)) {
throw $this->createNotFoundException('File not found');
}
return $this->file($filePath);
}
}
?>
3. Routing Configuration
File: config/routes.yaml
# config/routes.yaml
blog:
path: /blog/{slug}
controller: App\Controller\BlogController::show
requirements:
slug: '[a-z0-9-]+'
admin:
resource: '../src/Controller/Admin/'
type: attribute
prefix: /admin
api:
resource: '../src/Controller/Api/'
type: attribute
prefix: /api
methods: [GET, POST, PUT, DELETE]
Dependency Injection & Services
1. Service Definition
File: src/Service/EmailService.php
<?php
namespace App\Service;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Psr\Log\LoggerInterface;
class EmailService
{
private MailerInterface $mailer;
private LoggerInterface $logger;
private string $fromEmail;
public function __construct(
MailerInterface $mailer,
LoggerInterface $logger,
string $fromEmail = 'noreply@example.com'
) {
$this->mailer = $mailer;
$this->logger = $logger;
$this->fromEmail = $fromEmail;
}
public function sendWelcomeEmail(string $to, string $name): void
{
$email = (new Email())
->from($this->fromEmail)
->to($to)
->subject('Welcome!')
->html($this->renderWelcomeTemplate($name));
try {
$this->mailer->send($email);
$this->logger->info('Welcome email sent', ['to' => $to]);
} catch (\Exception $e) {
$this->logger->error('Failed to send welcome email', [
'to' => $to,
'error' => $e->getMessage()
]);
throw $e;
}
}
private function renderWelcomeTemplate(string $name): string
{
return "<h1>Welcome, {$name}!</h1><p>Thank you for joining us.</p>";
}
}
?>
2. Service Configuration
File: config/services.yaml
# config/services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
# Custom service configuration
App\Service\EmailService:
arguments:
$fromEmail: '%env(FROM_EMAIL)%'
# Alias for interface
App\Service\UserServiceInterface: '@App\Service\UserService'
# Public service
app.payment_service:
class: App\Service\PaymentService
public: true
parameters:
app.supported_locales: ['en', 'fr', 'es']
3. Using Services in Controller
<?php
namespace App\Controller;
use App\Service\EmailService;
use App\Service\UserService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class UserController extends AbstractController
{
private EmailService $emailService;
private UserService $userService;
public function __construct(
EmailService $emailService,
UserService $userService
) {
$this->emailService = $emailService;
$this->userService = $userService;
}
#[Route('/register', name: 'user_register')]
public function register(): Response
{
// Registration logic
$user = $this->userService->createUser([
'name' => 'John Doe',
'email' => 'john@example.com'
]);
// Send welcome email
$this->emailService->sendWelcomeEmail(
$user->getEmail(),
$user->getName()
);
return $this->json(['message' => 'User registered successfully']);
}
}
?>
4. Event Dispatcher
<?php
namespace App\Event;
use App\Entity\User;
use Symfony\Contracts\EventDispatcher\Event;
class UserRegisteredEvent extends Event
{
private User $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function getUser(): User
{
return $this->user;
}
}
// Event Listener
namespace App\EventListener;
use App\Event\UserRegisteredEvent;
use App\Service\EmailService;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener(event: UserRegisteredEvent::class)]
class UserRegisteredListener
{
private EmailService $emailService;
public function __construct(EmailService $emailService)
{
$this->emailService = $emailService;
}
public function __invoke(UserRegisteredEvent $event): void
{
$user = $event->getUser();
$this->emailService->sendWelcomeEmail(
$user->getEmail(),
$user->getName()
);
}
}
?>
Doctrine ORM
1. Entity
# Generate entity
php bin/console make:entity User
File: src/Entity/User.php
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: 'users')]
class User implements UserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
#[Assert\NotBlank]
#[Assert\Email]
private ?string $email = null;
#[ORM\Column]
private array $roles = [];
#[ORM\Column]
private ?string $password = null;
#[ORM\Column(length: 100)]
#[Assert\NotBlank]
#[Assert\Length(min: 2, max: 100)]
private ?string $name = null;
#[ORM\Column]
private ?\DateTimeImmutable $createdAt = null;
public function __construct()
{
$this->createdAt = new \DateTimeImmutable();
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): static
{
$this->email = $email;
return $this;
}
public function getUserIdentifier(): string
{
return (string) $this->email;
}
public function getRoles(): array
{
$roles = $this->roles;
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): static
{
$this->roles = $roles;
return $this;
}
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): static
{
$this->password = $password;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getCreatedAt(): ?\DateTimeImmutable
{
return $this->createdAt;
}
public function eraseCredentials(): void
{
// Clear sensitive data if needed
}
}
?>
2. Repository
File: src/Repository/UserRepository.php
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class UserRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
public function findActiveUsers(): array
{
return $this->createQueryBuilder('u')
->andWhere('u.status = :status')
->setParameter('status', 'active')
->orderBy('u.createdAt', 'DESC')
->getQuery()
->getResult();
}
public function findUsersByRole(string $role): array
{
return $this->createQueryBuilder('u')
->andWhere('u.roles LIKE :role')
->setParameter('role', '%"'.$role.'"%')
->getQuery()
->getResult();
}
public function findUsersCreatedAfter(\DateTimeInterface $date): array
{
return $this->createQueryBuilder('u')
->andWhere('u.createdAt > :date')
->setParameter('date', $date)
->orderBy('u.createdAt', 'ASC')
->getQuery()
->getResult();
}
public function countUsersByMonth(): array
{
return $this->createQueryBuilder('u')
->select('YEAR(u.createdAt) as year, MONTH(u.createdAt) as month, COUNT(u.id) as count')
->groupBy('year, month')
->orderBy('year', 'DESC')
->addOrderBy('month', 'DESC')
->getQuery()
->getResult();
}
}
?>
3. Database Operations
# Create database
php bin/console doctrine:database:create
# Generate migration
php bin/console make:migration
# Run migrations
php bin/console doctrine:migrations:migrate
# Load fixtures
php bin/console doctrine:fixtures:load
4. Using Doctrine in Controller
<?php
namespace App\Controller;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class UserManagementController extends AbstractController
{
#[Route('/users', name: 'user_list')]
public function list(UserRepository $userRepository): Response
{
$users = $userRepository->findActiveUsers();
return $this->render('user/list.html.twig', [
'users' => $users
]);
}
#[Route('/users/create', name: 'user_create', methods: ['POST'])]
public function create(Request $request, EntityManagerInterface $em): Response
{
$user = new User();
$user->setName($request->request->get('name'));
$user->setEmail($request->request->get('email'));
$user->setPassword(password_hash($request->request->get('password'), PASSWORD_DEFAULT));
$em->persist($user);
$em->flush();
$this->addFlash('success', 'User created successfully!');
return $this->redirectToRoute('user_list');
}
#[Route('/users/{id}/edit', name: 'user_edit', methods: ['PUT'])]
public function edit(User $user, Request $request, EntityManagerInterface $em): Response
{
$user->setName($request->request->get('name'));
$user->setEmail($request->request->get('email'));
$em->flush();
return $this->json(['message' => 'User updated successfully']);
}
#[Route('/users/{id}', name: 'user_delete', methods: ['DELETE'])]
public function delete(User $user, EntityManagerInterface $em): Response
{
$em->remove($user);
$em->flush();
return $this->json(['message' => 'User deleted successfully']);
}
}
?>
Tips dan Best Practices
โ Do (Lakukan)
- Gunakan dependency injection untuk semua services
- Manfaatkan Symfony components dan bundles
- Ikuti PSR standards dan Symfony best practices
- Gunakan environment variables untuk konfigurasi
- Write tests untuk business logic
- Optimalkan cache dan performance
โ Don't (Jangan)
- Jangan hardcode values dalam controller
- Jangan abaikan error handling dan logging
- Jangan skip validation dan security
- Jangan gunakan static methods tanpa alasan kuat
- Jangan commit sensitive data ke repository
- Jangan mengabaikan memory dan performance