Symfony - Framework PHP Enterprise

Dipublikasikan pada 15 Januari 2024 โ€ข โฑ๏ธ 50 menit baca

Prasyarat: Pemahaman mendalam tentang PHP OOP, design patterns, dan pengalaman dengan framework lain sangat disarankan.

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
Tutorial Saat Ini
Level: Lanjutan

Untuk yang sudah mahir PHP dan ingin mendalami

Daftar Isi
Tips Belajar