Symfony 8 ์™„๋ฒฝ ๊ฐ€์ด๋“œ: PHP 8.4 ๋ ˆ์ด์ง€ ์˜ค๋ธŒ์ ํŠธ, ๋ฉ€ํ‹ฐ์Šคํ… ํผ, 2026๋…„ ๋ฉด์ ‘ ๋Œ€๋น„๊นŒ์ง€

Symfony 8์€ PHP 8.4๋ฅผ ํ•„์ˆ˜๋กœ ์š”๊ตฌํ•˜๋ฉฐ ๋„ค์ดํ‹ฐ๋ธŒ ๋ ˆ์ด์ง€ ์˜ค๋ธŒ์ ํŠธ, AbstractFlowType, ํ˜ธ์ถœ ๊ฐ€๋Šฅ ์ปค๋งจ๋“œ ๋“ฑ ๋‹ค์ˆ˜์˜ ์‹ ๊ธฐ๋Šฅ์„ ํƒ‘์žฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฃผ์š” ๊ธฐ๋Šฅ์„ ์ฝ”๋“œ ์˜ˆ์ œ์™€ ํ•จ๊ป˜ ๋ถ„์„ํ•˜๊ณ  2026๋…„ ๋ฉด์ ‘ ๋Œ€๋น„ ํฌ์ธํŠธ๋ฅผ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Symfony 8 ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ๊ณผ PHP 8.4 ๋ ˆ์ด์ง€ ์˜ค๋ธŒ์ ํŠธ

2025๋…„ 11์›”์— ๋ฆด๋ฆฌ์Šค๋œ Symfony 8์€ Symfony 7.x ์ฃผ๊ธฐ ๋™์•ˆ ๋ˆ„์ ๋œ ๋ชจ๋“  ์ง€์› ์ค‘๋‹จ(deprecation) ํ•ญ๋ชฉ์„ ์ œ๊ฑฐํ•˜๊ณ , PHP 8.4 ์ด์ƒ์„ ํ•„์ˆ˜ ์š”๊ตฌ์‚ฌํ•ญ์œผ๋กœ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ์š”๊ตฌ์‚ฌํ•ญ ๋•๋ถ„์— ๋„ค์ดํ‹ฐ๋ธŒ ๋ ˆ์ด์ง€ ์˜ค๋ธŒ์ ํŠธ, ํ”„๋กœํผํ‹ฐ ํ›…, ์ƒˆ๋กœ์šด HTML5 ํŒŒ์„œ ๋“ฑ PHP ์–ธ์–ด ์ˆ˜์ค€์˜ ๊ธฐ๋Šฅ์ด ํ”„๋ ˆ์ž„์›Œํฌ ๋‚ด๋ถ€์—์„œ ์ง์ ‘ ํ™œ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” ์ผ์ƒ์ ์ธ ๊ฐœ๋ฐœ ์›Œํฌํ”Œ๋กœ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ์ฃผ์š” ๊ธฐ๋Šฅ, ๋ฉด์ ‘ ์ค€๋น„์— ๋„์›€์ด ๋˜๋Š” ํฌ์ธํŠธ, ๊ทธ๋ฆฌ๊ณ  ํ”„๋กœ๋•์…˜ ์—…๊ทธ๋ ˆ์ด๋“œ ์‹œ ์ฃผ์˜ํ•ด์•ผ ํ•  ์‚ฌํ•ญ์„ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์ƒ์„ธํžˆ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.

Symfony 8 ํ•ต์‹ฌ ์š”์•ฝ

Symfony 8.0์€ PHP 8.4 ์ด์ƒ์„ ํ•„์ˆ˜๋กœ ํ•˜๋ฉฐ, ๋„ค์ดํ‹ฐ๋ธŒ ๋ฉ€ํ‹ฐ์Šคํ… ํผ(AbstractFlowType), #[Argument]์™€ #[Option] ์†์„ฑ์„ ํ™œ์šฉํ•œ ํ˜ธ์ถœ ๊ฐ€๋Šฅ ์ฝ˜์†” ์ปค๋งจ๋“œ, 3๊ฐœ์˜ ์ƒˆ๋กœ์šด JSON/ObjectMapper ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ DI ์ปจํ…Œ์ด๋„ˆ์™€ Doctrine ๋ชจ๋‘์—์„œ ํ”„๋ก์‹œ ์ฝ”๋“œ ์ƒ์„ฑ์ด PHP 8.4 ๋„ค์ดํ‹ฐ๋ธŒ ๋ ˆ์ด์ง€ ์˜ค๋ธŒ์ ํŠธ๋กœ ๋Œ€์ฒด๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋„ค์ดํ‹ฐ๋ธŒ ๋ ˆ์ด์ง€ ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ํ”„๋ก์‹œ ์ฝ”๋“œ ์ƒ์„ฑ์„ ๋Œ€์ฒดํ•˜๋‹ค

PHP 8.4์—์„œ๋Š” ReflectionClass::newLazyGhost()์™€ ReflectionClass::newLazyProxy()๋ฅผ ํ†ตํ•ด ์—”์ง„ ์ˆ˜์ค€์˜ ๋ ˆ์ด์ง€ ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ๋„์ž…๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Symfony 8์€ ์ด ๊ธฐ๋Šฅ์„ ์ง์ ‘ ํ™œ์šฉํ•˜์—ฌ, DependencyInjection ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋” ์ด์ƒ ๋ ˆ์ด์ง€ ์„œ๋น„์Šค์šฉ ํ”„๋ก์‹œ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ์ฒซ ๋ฒˆ์งธ ํ”„๋กœํผํ‹ฐ ์ ‘๊ทผ ์‹œ ์ดˆ๊ธฐํ™”๋˜๋Š” ๊ณ ์ŠคํŠธ ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

์‹ค์งˆ์ ์ธ ์˜ํ–ฅ์€ ์ƒ๋‹นํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ์กด์— Symfony์˜ ํ”„๋ก์‹œ ๊ธฐ๋ฐ˜ ๋ ˆ์ด์ง€ ๋กœ๋”ฉ๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š์•˜๋˜ final ํด๋ž˜์Šค์™€ readonly ํด๋ž˜์Šค๊ฐ€ ๋ณ„๋„์˜ ์šฐํšŒ ๋ฐฉ๋ฒ• ์—†์ด ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. Doctrine๋„ ๋™์ผํ•œ ํ˜œํƒ์„ ๋ฐ›์•„ ์—”ํ‹ฐํ‹ฐ ํ”„๋ก์‹œ์˜ ์ฝ”๋“œ ์ƒ์„ฑ์ด ๋ถˆํ•„์š”ํ•ด์กŒ์œผ๋ฉฐ, 10๋…„ ์ด์ƒ ์ง€์†๋œ ์œ ์ง€๋ณด์ˆ˜ ๋ถ€๋‹ด์ด ํ•ด์†Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

src/Service/HeavyReportGenerator.phpphp
namespace App\Service;

use Symfony\Component\DependencyInjection\Attribute\Lazy;

#[Lazy]
final readonly class HeavyReportGenerator
{
    public function __construct(
        private DatabaseConnection $db,
        private PdfEngine $pdf,
        private CacheInterface $cache,
    ) {
        // Constructor runs only when a method is actually called
    }

    public function generate(int $reportId): string
    {
        $data = $this->db->fetchReport($reportId);
        return $this->pdf->render($data);
    }
}

#[Lazy] ์†์„ฑ์€ ํ•ด๋‹น ์„œ๋น„์Šค๋ฅผ ๊ณ ์ŠคํŠธ ์˜ค๋ธŒ์ ํŠธ๋กœ ์ธ์Šคํ„ด์Šคํ™”ํ•˜๋„๋ก ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ปจํ…Œ์ด๋„ˆ๋Š” HeavyReportGenerator์™€ ์™ธํ˜•์ƒ ๋™์ผํ•œ ์…ธ์„ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค. ์ƒ์„ฑ์ž์™€ 3๊ฐœ์˜ ์ฃผ์ž…๋œ ์˜์กด์„ฑ์€ generate()๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ์—๋งŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์กฐ๊ฑด๋ถ€๋กœ ์‚ฌ์šฉ๋˜๋Š” ์„œ๋น„์Šค(ํŠน์ • ๊ด€๋ฆฌ์ž ์ž‘์—…์—์„œ๋งŒ ํŠธ๋ฆฌ๊ฑฐ๋˜๋Š” ๋ณด๊ณ ์„œ ์ƒ์„ฑ ๋“ฑ)์˜ ๊ฒฝ์šฐ, ๋งค ์š”์ฒญ๋งˆ๋‹ค ๋ฐœ์ƒํ•˜๋˜ ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ๊ณผ ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น์ด ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.

#[Autowire(lazy: true)] ๋ณ€ํ˜•์„ ์‚ฌ์šฉํ•˜๋ฉด, ์„œ๋น„์Šค ํด๋ž˜์Šค ์ž์ฒด๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ ๋„ ํ˜ธ์ถœ ์ธก์—์„œ ๋ ˆ์ด์ง€ ์ฃผ์ž…์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

src/Controller/DashboardController.phpphp
namespace App\Controller;

use Symfony\Component\DependencyInjection\Attribute\Autowire;

class DashboardController
{
    public function __construct(
        #[Autowire(lazy: true)]
        private HeavyReportGenerator $reportGenerator,
    ) {}
}

๋ฉด์ ‘์—์„œ๋Š” ๊ณ ์ŠคํŠธ ์˜ค๋ธŒ์ ํŠธ์™€ ๋ฒ„์ถ”์–ผ ํ”„๋ก์‹œ์˜ ์ฐจ์ด์— ๋Œ€ํ•œ ์งˆ๋ฌธ์ด ์ž์ฃผ ์ถœ์ œ๋ฉ๋‹ˆ๋‹ค. ๊ณ ์ŠคํŠธ ์˜ค๋ธŒ์ ํŠธ๋Š” ์›๋ž˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ทธ ์ž๋ฆฌ์—์„œ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. ๋ฒ„์ถ”์–ผ ํ”„๋ก์‹œ๋Š” ์™„์ „ํžˆ ์ดˆ๊ธฐํ™”๋œ ๋ณ„๋„์˜ ์ธ์Šคํ„ด์Šค์— ์ฒ˜๋ฆฌ๋ฅผ ์œ„์ž„ํ•ฉ๋‹ˆ๋‹ค. Symfony๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ณ ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ํŒฉํ† ๋ฆฌ๊ฐ€ ๊ด€๋ จ๋œ ๊ฒฝ์šฐ ์ž๋™์œผ๋กœ ํ”„๋ก์‹œ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

AbstractFlowType์„ ํ™œ์šฉํ•œ ๋ฉ€ํ‹ฐ์Šคํ… ํผ

Symfony 8 ์ด์ „์—๋Š” ๋ฉ€ํ‹ฐ์Šคํ… ํผ ๊ตฌํ˜„์„ ์œ„ํ•ด CraueFormFlowBundle ๊ฐ™์€ ์„œ๋“œํŒŒํ‹ฐ ๋ฒˆ๋“ค์ด ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ AbstractFlowType์„ ์ž์ฒด์ ์œผ๋กœ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์Šคํ…๋ณ„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ์กฐ๊ฑด๋ถ€ ๋ถ„๊ธฐ, ๋‚ด์žฅ ๋„ค๋น„๊ฒŒ์ด์…˜์„ ๊ฐ–์ถ˜ ๋„ค์ดํ‹ฐ๋ธŒ ํผ ํ”Œ๋กœ์šฐ ์—”์ง„์ž…๋‹ˆ๋‹ค.

src/Form/UserSignUpType.phpphp
namespace App\Form;

use Symfony\Component\Form\Flow\AbstractFlowType;
use Symfony\Component\Form\Flow\FormFlowBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class UserSignUpType extends AbstractFlowType
{
    public function buildFormFlow(FormFlowBuilderInterface $builder, array $options): void
    {
        $builder->addStep('personal', UserPersonalType::class);
        $builder->addStep('professional', UserProfessionalType::class);
        $builder->addStep('account', UserAccountType::class);

        $builder->add('navigator', NavigatorFlowType::class);
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => UserSignUp::class,
            'step_property_path' => 'currentStep',
        ]);
    }
}

๊ฐ ์Šคํ… ์ด๋ฆ„์€ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ทธ๋ฃน์œผ๋กœ๋„ ๊ธฐ๋Šฅํ•ฉ๋‹ˆ๋‹ค. UserSignUp ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค๋Š” ํ™œ์„ฑ ์Šคํ…๋งŒ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•ด ๊ทธ๋ฃน ์ œ์•ฝ ์กฐ๊ฑด์ด ์ ์šฉ๋œ #[Valid]๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

src/DTO/UserSignUp.phpphp
namespace App\DTO;

use Symfony\Component\Validator\Constraints as Assert;

class UserSignUp
{
    public function __construct(
        #[Assert\Valid(groups: ['personal'])]
        public Personal $personal = new Personal(),

        #[Assert\Valid(groups: ['professional'])]
        public Professional $professional = new Professional(),

        #[Assert\Valid(groups: ['account'])]
        public Account $account = new Account(),

        public string $currentStep = 'personal',
    ) {}
}

์ปจํŠธ๋กค๋Ÿฌ๋Š” ํ‘œ์ค€ Symfony ํผ ์ฒ˜๋ฆฌ ํŒจํ„ด์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. isFinished()๋Š” ๋งˆ์ง€๋ง‰ ์Šคํ…์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ†ต๊ณผํ•œ ํ›„์—๋งŒ true๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

src/Controller/SignUpController.phpphp
#[Route('/signup')]
public function __invoke(Request $request): Response
{
    $flow = $this->createForm(UserSignUpType::class, new UserSignUp())
        ->handleRequest($request);

    if ($flow->isSubmitted() && $flow->isValid() && $flow->isFinished()) {
        $this->userService->register($flow->getData());
        return $this->redirectToRoute('app_signup_success');
    }

    return $this->render('signup/flow.html.twig', [
        'form' => $flow->getStepForm(),
    ]);
}

๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฒ„ํŠผ(NextFlowType, PreviousFlowType, FinishFlowType, ResetFlowType)์€ include_if๋ฅผ ํ†ตํ•œ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง, skip์„ ํ†ตํ•œ ์Šคํ… ๊ฑด๋„ˆ๋›ฐ๊ธฐ, back_to๋ฅผ ํ†ตํ•œ ์ง์ ‘ ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์ปค์Šคํ…€ JavaScript ์ƒํƒœ ๊ด€๋ฆฌ ์—†์ด๋„ ์‹ค๋ฌด์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋Œ€๋ถ€๋ถ„์˜ ์œ„์ €๋“œ ํŒจํ„ด์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Symfony ๋ฉด์ ‘ ์ค€๋น„๊ฐ€ ๋˜์…จ๋‚˜์š”?

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, flashcards, ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์—ฐ์Šตํ•˜์„ธ์š”.

์ž…๋ ฅ ์†์„ฑ์„ ํ™œ์šฉํ•œ ํ˜ธ์ถœ ๊ฐ€๋Šฅ ์ปค๋งจ๋“œ

์ฝ˜์†” ์ปค๋งจ๋“œ์—์„œ ๋” ์ด์ƒ Command ๊ธฐ๋ณธ ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•˜๊ฑฐ๋‚˜ configure()๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. __invoke() ๋ฉ”์„œ๋“œ๊ฐ€ PHP ์†์„ฑ์„ ํ†ตํ•ด ์ธ์ž์™€ ์˜ต์…˜์„ ์ง์ ‘ ์ „๋‹ฌ๋ฐ›์Šต๋‹ˆ๋‹ค.

src/Command/CreateUserCommand.phpphp
namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
    name: 'app:create-user',
    description: 'Create a new user account',
)]
class CreateUserCommand
{
    public function __invoke(
        SymfonyStyle $io,
        #[Argument(description: 'The username for the new account')]
        string $username,
        #[Argument(description: 'The email address')]
        string $email,
        #[Option(shortcut: 'r', description: 'Role to assign')]
        string $role = 'ROLE_USER',
        #[Option(description: 'Activate immediately')]
        bool $activate = false,
    ): int {
        // User creation logic
        $io->success(sprintf('User "%s" created with role %s.', $username, $role));
        return Command::SUCCESS;
    }
}

Symfony 8์—์„œ๋Š” Backed Enum์ด ์ธ์ž์™€ ์˜ต์…˜์—์„œ ์ง€์›๋ฉ๋‹ˆ๋‹ค. ๋ฌธ์ž์—ด ์ž…๋ ฅ์ด ํ•ด๋‹นํ•˜๋Š” Enum ์ผ€์ด์Šค๋กœ ์ž๋™ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค.

src/Enum/CloudRegion.phpphp
enum CloudRegion: string {
    case UsEast = 'us-east';
    case UsWest = 'us-west';
    case EuWest = 'eu-west';
}

// In the command
public function __invoke(
    SymfonyStyle $io,
    #[Argument] CloudRegion $region,
    #[Option] ?ServerSize $size = null,
): int {
    $io->info(sprintf('Deploying to %s', $region->value));
    return Command::SUCCESS;
}

ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋งŽ์€ ์ปค๋งจ๋“œ์˜ ๊ฒฝ์šฐ, #[MapInput]์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž…๋ ฅ์„ DTO์— ๋งคํ•‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

src/Command/Input/DeployInput.phpphp
class DeployInput
{
    #[Argument]
    public CloudRegion $region;

    #[Option]
    public string $branch = 'main';

    #[Option(shortcut: 'f')]
    public bool $force = false;
}

// src/Command/DeployCommand.php
#[AsCommand(name: 'app:deploy')]
class DeployCommand
{
    public function __invoke(
        SymfonyStyle $io,
        #[MapInput] DeployInput $input,
    ): int {
        // Access $input->region, $input->branch, $input->force
        return Command::SUCCESS;
    }
}

์ด ํŒจํ„ด์œผ๋กœ configure() + execute()์˜ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๊ฐ€ ์ œ๊ฑฐ๋˜๋ฉฐ, DTO๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•˜์—ฌ ์ปค๋งจ๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

JsonStreamer, JsonPath, ObjectMapper ์ปดํฌ๋„ŒํŠธ

Symfony 8์—๋Š” 3๊ฐœ์˜ ์ƒˆ๋กœ์šด ๋…๋ฆฝํ˜• ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

JsonStreamer๋Š” ์ „์ฒด ๋ฌธ์„œ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ๋กœ๋“œํ•˜์ง€ ์•Š๊ณ  ๋Œ€์šฉ๋Ÿ‰ JSON ํŽ˜์ด๋กœ๋“œ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋ฉ”๊ฐ€๋ฐ”์ดํŠธ ๊ทœ๋ชจ์˜ ์‘๋‹ต์„ ๋‹ค๋ฃจ๋Š” API(๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ๋‚ด๋ณด๋‚ด๊ธฐ, ๋ถ„์„ ํ”ผ๋“œ, ๋กœ๊ทธ ์ˆ˜์ง‘ ๋“ฑ)์—์„œ json_decode()์˜ ๋ฉ”๋ชจ๋ฆฌ ํ•œ๊ณ„ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

JsonPath๋Š” ์ค‘์ฒฉ๋œ JSON ๊ตฌ์กฐ๋ฅผ ํƒ์ƒ‰ํ•˜๊ธฐ ์œ„ํ•œ RFC 9535์˜ ์„œ๋ธŒ์…‹์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด ์‘๋‹ต์„ ๋””์ฝ”๋”ฉํ•˜๊ณ  ๋ฐฐ์—ด์„ ์ˆ˜๋™์œผ๋กœ ์ˆœํšŒํ•˜๋Š” ๋Œ€์‹ , ๊ฒฝ๋กœ ํ‘œํ˜„์‹์œผ๋กœ ๋Œ€์ƒ ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ObjectMapper๋Š” PHP ์†์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ DTO์™€ ๋ฐฐ์—ด/JSON ๊ฐ„์˜ ๋ณ€ํ™˜์„ ์ˆ˜ํ–‰ํ•˜๋ฉฐ, ์ˆ˜์ž‘์—…์œผ๋กœ ์ž‘์„ฑํ•˜๋˜ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ๋กœ์ง์„ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค. Serializer ์ปดํฌ๋„ŒํŠธ์™€ ๊ฒฐํ•ฉํ•˜๋ฉด API ์š”์ฒญ/์‘๋‹ต ๋งคํ•‘์˜ ์ „์ฒด ๋ผ์ดํ”„์‚ฌ์ดํด์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฉด์ ‘ ์ถœ์ œ ๊ฒฝํ–ฅ

2026๋…„ Symfony ๋ฉด์ ‘์—์„œ๋Š” ๋ ˆ์ด์ง€ ์˜ค๋ธŒ์ ํŠธ(๊ณ ์ŠคํŠธ vs ํ”„๋ก์‹œ), AbstractFlowType์˜ ๋ผ์ดํ”„์‚ฌ์ดํด, configure()์—์„œ ํ˜ธ์ถœ ๊ฐ€๋Šฅ ์ปค๋งจ๋“œ๋กœ์˜ ์ „ํ™˜์ด ๋นˆ์ถœ ์ฃผ์ œ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํŒจํ„ด์— ๋Œ€ํ•œ ์ดํ•ด๋Š” ๋ ˆ๊ฑฐ์‹œ ์ง€์‹์ด ์•„๋‹Œ ํ˜„์žฌ ํ”„๋ ˆ์ž„์›Œํฌ ๋ฒ„์ „์— ๋Œ€ํ•œ ์ˆ™๋ จ๋„๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

๋ณด์•ˆ ๋ฐ ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜ ๊ฐœ์„ ์‚ฌํ•ญ

CSRF ๋ณดํ˜ธ๊ฐ€ ๋” ์ด์ƒ ์„œ๋ฒ„ ์ธก ์„ธ์…˜์„ ํ•„์š”๋กœ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ์Šคํ…Œ์ดํŠธ๋ฆฌ์Šค CSRF ๊ตฌํ˜„์€ HTTP ์บ์‹ฑ๊ณผ ํ˜ธํ™˜๋˜์–ด, API ์šฐ์„  ์•„ํ‚คํ…์ฒ˜ ๋ฐ CDN ์บ์‹œ ํŽ˜์ด์ง€์—์„œ ํ™œ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

Messenger ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฉ”์‹œ์ง€ ์„œ๋ช…์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ํŽ˜์ด๋กœ๋“œ์— ๋Œ€ํ•œ ์•”ํ˜ธํ™” ์„œ๋ช…์œผ๋กœ ํ”„๋กœ๋“€์„œ์™€ ์ปจ์Šˆ๋จธ ๊ฐ„์˜ ์œ„๋ณ€์กฐ๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๋ฉ”์‹œ์ง€ ๋ฌด๊ฒฐ์„ฑ์ด ์ค‘์š”ํ•œ ์‹œ์Šคํ…œ(๊ฒฐ์ œ ์ฒ˜๋ฆฌ, ๊ฐ์‚ฌ ์ถ”์  ๋“ฑ)์—์„œ ์ปค์Šคํ…€ ์„œ๋ช… ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ƒˆ๋กœ์šด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ œ์•ฝ ์กฐ๊ฑด์€ ๋ฌธ์ž ์„ธํŠธ, MAC ์ฃผ์†Œ, ISO ์ฃผ ๋ฒˆํ˜ธ, ๋‹จ์–ด ์ˆ˜, YAML ๊ตฌ๋ฌธ, ์Šฌ๋Ÿฌ๊ทธ, Twig ํ…œํ”Œ๋ฆฟ, ๋™์˜์ƒ ํŒŒ์ผ ๋“ฑ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. #[Slug] ์ œ์•ฝ ์กฐ๊ฑด ํ•˜๋‚˜๋งŒ์œผ๋กœ๋„ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋˜ ์ปค์Šคํ…€ ๋ฐธ๋ฆฌ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹œํ๋ฆฌํ‹ฐ ๋ณดํ„ฐ(Security Voter)๊ฐ€ ํ”„๋กœํŒŒ์ผ๋Ÿฌ์—์„œ ๊ฒฐ์ • ์ด์œ ๋ฅผ ์„ค๋ช…ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด์— ์–ด๋–ค ๋ณดํ„ฐ๊ฐ€ ์ ‘๊ทผ์„ ๊ฑฐ๋ถ€ํ–ˆ๋Š”์ง€ ์ถ”์ธกํ•ด์•ผ ํ–ˆ๋˜ ์ธ๊ฐ€ ๋กœ์ง ๋””๋ฒ„๊น…์ด, Symfony ํ”„๋กœํŒŒ์ผ๋Ÿฌ ํˆด๋ฐ”์—์„œ์˜ ์ง์ ‘ ์กฐํšŒ๋กœ ๋ฐ”๋€Œ์—ˆ์Šต๋‹ˆ๋‹ค.

์—…๊ทธ๋ ˆ์ด๋“œ ๊ฒฝ๋กœ

Symfony 8.0์€ 7.x ์ฃผ๊ธฐ์˜ ๋ชจ๋“  ์ง€์› ์ค‘๋‹จ ํ•ญ๋ชฉ์„ ์‚ญ์ œํ•˜์—ฌ, 13,202์ค„์˜ ํ•˜์œ„ ํ˜ธํ™˜์„ฑ ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ถŒ์žฅ ์ ‘๊ทผ ๋ฐฉ์‹์€ ๋จผ์ € Symfony 7.4๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๊ณ , php bin/phpunit --display-deprecations๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๋ชจ๋“  ๊ฒฝ๊ณ ๋ฅผ ์ˆ˜์ •ํ•œ ํ›„ 8.0์œผ๋กœ ์ „ํ™˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. 7.4๋ฅผ ๊ฑด๋„ˆ๋›ธ ๊ฒฝ์šฐ ์ œ๊ฑฐ๋œ API๋กœ ์ธํ•œ ๋Ÿฐํƒ€์ž„ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

PHP 8.4 ํ”„๋กœํผํ‹ฐ ํ›…๊ณผ Symfony์—์„œ์˜ ํ™œ์šฉ

ํ”„๋กœํผํ‹ฐ ํ›…(ํด๋ž˜์Šค ํ”„๋กœํผํ‹ฐ์— ์ง์ ‘ ์ •์˜๋˜๋Š” get๊ณผ set)์€ Symfony ์ƒํƒœ๊ณ„์™€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ†ตํ•ฉ๋ฉ๋‹ˆ๋‹ค. Doctrine ์—”ํ‹ฐํ‹ฐ๋Š” ๊ณ„์‚ฐ๋œ ํ”„๋กœํผํ‹ฐ์— ํ›…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํผ ํƒ€์ž…์€ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜์— ํ›…์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ œ์•ฝ ์กฐ๊ฑด์€ ํ›…์ด ์ ์šฉ๋œ ํ”„๋กœํผํ‹ฐ์—์„œ ์ˆ˜์ • ์—†์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

src/Entity/Product.phpphp
class Product
{
    public string $name {
        set(string $value) {
            $this->name = trim($value);
        }
    }

    public float $price {
        set(float $value) {
            if ($value < 0) {
                throw new \InvalidArgumentException('Price cannot be negative');
            }
            $this->price = $value;
        }
    }

    public float $priceWithTax {
        get => $this->price * 1.20;
    }
}

ํ”„๋กœํผํ‹ฐ ํ›…์œผ๋กœ ์ธํ•ด getter/setter ์Œ์˜ ํ•„์š”์„ฑ์ด ์ค„์–ด๋“ค๊ณ , ๋ถˆ๋ณ€ ์กฐ๊ฑด ์ ์šฉ์ด ํ”„๋กœํผํ‹ฐ ์ •์˜ ๋‚ด๋ถ€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. Symfony ํผ์—์„œ๋Š” ํŠธ๋žœ์Šคํฌ๋จธ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๊ฐ€ ๊ฐ์†Œํ•ฉ๋‹ˆ๋‹ค. Doctrine ์—”ํ‹ฐํ‹ฐ์—์„œ๋Š” ๊ณ„์‚ฐ ๊ฐ’์ด ์˜์กดํ•˜๋Š” ๋ฐ์ดํ„ฐ ๊ฐ€๊นŒ์ด์— ์œ„์น˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋ฉด์ ‘์—์„œ ์ค€๋น„ํ•ด์•ผ ํ•  ์งˆ๋ฌธ

Symfony 8๋กœ ์ธํ•ด ๋ฉด์ ‘์—์„œ ์ถœ์ œ๋˜๋Š” ๋‚ด์šฉ์ด ๋ณ€ํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์ฃผ์ œ๋“ค์ด Symfony ๋ฉด์ ‘ ์ค€๋น„์—์„œ ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋“ฑ์žฅํ•ฉ๋‹ˆ๋‹ค.

  • ๋ ˆ์ด์ง€ ์˜ค๋ธŒ์ ํŠธ: ๊ณ ์ŠคํŠธ ์˜ค๋ธŒ์ ํŠธ์™€ ๋ฒ„์ถ”์–ผ ํ”„๋ก์‹œ์˜ ์ฐจ์ด๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€. Symfony๋Š” ์–ด๋–ค ๊ฒฝ์šฐ์— ์–ด๋А ๊ฒƒ์„ ์„ ํƒํ•˜๋Š”๊ฐ€. PHP 8.4์—์„œ final readonly ์„œ๋น„์Šค์˜ ๋ ˆ์ด์ง€ ๋กœ๋”ฉ์ด ๊ฐ€๋Šฅํ•ด์ง„ ์ด์œ ๋Š” ๋ฌด์—‡์ธ๊ฐ€.
  • ๋ฉ€ํ‹ฐ์Šคํ… ํผ: AbstractFlowType์€ ์Šคํ…๋ณ„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š”๊ฐ€. step_property_path์˜ ์—ญํ• ์€ ๋ฌด์—‡์ธ๊ฐ€. ์กฐ๊ฑด๋ถ€ ์Šคํ…์€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”๊ฐ€.
  • ํ˜ธ์ถœ ๊ฐ€๋Šฅ ์ปค๋งจ๋“œ: ํ˜ธ์ถœ ๊ฐ€๋Šฅ ์ปค๋งจ๋“œ์—์„œ configure()๋ฅผ ๋Œ€์ฒดํ•˜๋Š” ๊ฒƒ์€ ๋ฌด์—‡์ธ๊ฐ€. #[MapInput]์€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”๊ฐ€. #[Option] ๊ธฐ๋ณธ๊ฐ’์˜ ๊ทœ์น™์€ ๋ฌด์—‡์ธ๊ฐ€.
  • ์ƒˆ ์ปดํฌ๋„ŒํŠธ: JsonStreamer๊ฐ€ json_decode()๋ณด๋‹ค ์„ ํ˜ธ๋˜๋Š” ๊ฒฝ์šฐ๋Š” ์–ธ์ œ์ธ๊ฐ€. ObjectMapper๊ฐ€ Serializer๋กœ๋Š” ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•˜๋Š” ์–ด๋–ค ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š”๊ฐ€.
  • ์—…๊ทธ๋ ˆ์ด๋“œ ์ „๋žต: 8.0 ์ด์ „์— 7.4๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋˜๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ธ๊ฐ€. 13,202์ค„์˜ ์ง€์› ์ค‘๋‹จ ์ฝ”๋“œ ์ œ๊ฑฐ๊ฐ€ ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์— ์žˆ์–ด ์˜๋ฏธํ•˜๋Š” ๋ฐ”๋Š” ๋ฌด์—‡์ธ๊ฐ€.

์ด๋Ÿฌํ•œ ์ฃผ์ œ๋“ค์€ ์‹ค์ œ ์ฝ”๋“œ๋กœ ์—ฐ์Šตํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. Symfony ์ฝ˜์†” ์ปค๋งจ๋“œ ๋ชจ๋“ˆ๊ณผ Doctrine ์‹ฌํ™” ๋ชจ๋“ˆ์ด ๊ฐ€์žฅ ๋นˆ๋ฒˆํ•˜๊ฒŒ ํ…Œ์ŠคํŠธ๋˜๋Š” ์˜์—ญ์„ ๋‹ค๋ฃจ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฐ์Šต์„ ์‹œ์ž‘ํ•˜์„ธ์š”!

๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์ง€์‹์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

๊ฒฐ๋ก 

  • Symfony 8์€ PHP 8.4๋ฅผ ํ•„์ˆ˜๋กœ ํ•˜๋ฉฐ ๋„ค์ดํ‹ฐ๋ธŒ ๋ ˆ์ด์ง€ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์ „๋ฉด ๋„์ž…ํ•˜์—ฌ, DI ์ปจํ…Œ์ด๋„ˆ์™€ Doctrine ORM ๋ชจ๋‘์—์„œ ํ”„๋ก์‹œ ์ฝ”๋“œ ์ƒ์„ฑ์„ ์ œ๊ฑฐ
  • AbstractFlowType์ด ๋ฉ€ํ‹ฐ์Šคํ… ํผ์šฉ ์„œ๋“œํŒŒํ‹ฐ ๋ฒˆ๋“ค์„ ๋Œ€์ฒดํ•˜๋ฉฐ, ์Šคํ…๋ณ„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ทธ๋ฃน๊ณผ ๋‚ด์žฅ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฒ„ํŠผ์„ ์ œ๊ณต
  • #[Argument], #[Option], #[MapInput]์„ ํ™œ์šฉํ•œ ํ˜ธ์ถœ ๊ฐ€๋Šฅ ์ปค๋งจ๋“œ๊ฐ€ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  Backed Enum๊ณผ DTO๋ฅผ ์ง€์›
  • 3๊ฐœ์˜ ์ƒˆ ์ปดํฌ๋„ŒํŠธ(JsonStreamer, JsonPath, ObjectMapper)๊ฐ€ ์™ธ๋ถ€ ์˜์กด์„ฑ ์—†์ด JSON ์ฒ˜๋ฆฌ์™€ ์˜ค๋ธŒ์ ํŠธ ๋งคํ•‘์„ ์ˆ˜ํ–‰
  • ์Šคํ…Œ์ดํŠธ๋ฆฌ์Šค CSRF, ๋ฉ”์‹œ์ง€ ์„œ๋ช…, ์‹œํ๋ฆฌํ‹ฐ ๋ณดํ„ฐ ๋””๋ฒ„๊น…, 20๊ฐœ ์ด์ƒ์˜ ์ƒˆ๋กœ์šด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ œ์•ฝ ์กฐ๊ฑด์ด ๋ณด์•ˆ๊ณผ ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜์„ ๊ฐ•ํ™”
  • PHP 8.4์˜ ํ”„๋กœํผํ‹ฐ ํ›…์ด Doctrine ์—”ํ‹ฐํ‹ฐ, Symfony ํผ, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ œ์•ฝ ์กฐ๊ฑด๊ณผ ํˆฌ๋ช…ํ•˜๊ฒŒ ํ†ตํ•ฉ
  • ์—…๊ทธ๋ ˆ์ด๋“œ ๊ฒฝ๋กœ๋Š” Symfony 7.4๋ฅผ ๊ฑฐ์ณ์•ผ ํ•จ: 8.0์œผ๋กœ ์ „ํ™˜ํ•˜๊ธฐ ์ „์— ๋ชจ๋“  ์ง€์› ์ค‘๋‹จ ๊ฒฝ๊ณ ๋ฅผ ์ˆ˜์ •ํ•  ๊ฒƒ

์—ฐ์Šต์„ ์‹œ์ž‘ํ•˜์„ธ์š”!

๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์ง€์‹์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

ํƒœ๊ทธ

#symfony
#php
#symfony-8
#php-84
#lazy-objects

๊ณต์œ 

๊ด€๋ จ ๊ธฐ์‚ฌ

Symfony 7 API Platform ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค ์™„๋ฒฝ ๊ฐ€์ด๋“œ

Symfony 7: API Platform๊ณผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

Symfony 7๊ณผ API Platform 4๋กœ ์ „๋ฌธ์ ์ธ REST API๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ์™„๋ฒฝ ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค. State Provider, Processor, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ, ์ง๋ ฌํ™”๋ฅผ ์‹ค์ „ ์˜ˆ์ œ์™€ ํ•จ๊ป˜ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

Laravel ๋ฏธ๋“ค์›จ์–ด ์•„ํ‚คํ…์ฒ˜ - ์š”์ฒญ ํŒŒ์ดํ”„๋ผ์ธ, ์ธ์ฆ, ์†๋„ ์ œํ•œ ๊ตฌ์กฐ ๋‹ค์ด์–ด๊ทธ๋žจ

Laravel Middleware ์™„๋ฒฝ ๊ฐ€์ด๋“œ: ์ธ์ฆ, ์†๋„ ์ œํ•œ, ์ปค์Šคํ…€ ๋ฏธ๋“ค์›จ์–ด ๊ตฌ์ถ•

Laravel ๋ฏธ๋“ค์›จ์–ด์˜ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์กฐ๋ถ€ํ„ฐ ์ธ์ฆ, ์†๋„ ์ œํ•œ(Rate Limiting), ์ปค์Šคํ…€ ๋ฏธ๋“ค์›จ์–ด ์ž‘์„ฑ, ํ”„๋กœ๋•์…˜ ํŒจํ„ด๊นŒ์ง€ ์‹ค๋ฌด ์ฝ”๋“œ ์˜ˆ์ œ์™€ ํ•จ๊ป˜ ์ฒด๊ณ„์ ์œผ๋กœ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Laravel๊ณผ PHP ๋ฉด์ ‘ ์งˆ๋ฌธ - ์ข…ํ•ฉ ๊ฐ€์ด๋“œ

Laravel๊ณผ PHP ๋ฉด์ ‘ ์งˆ๋ฌธ: 2026๋…„ ํ•ต์‹ฌ 25์„ 

Laravel๊ณผ PHP ๋ฉด์ ‘์—์„œ ๊ฐ€์žฅ ์ž์ฃผ ์ถœ์ œ๋˜๋Š” 25๊ฐ€์ง€ ์งˆ๋ฌธ์„ ์ƒ์„ธํžˆ ๋‹ค๋ฃน๋‹ˆ๋‹ค. Eloquent ORM, ๋ฏธ๋“ค์›จ์–ด, ํ, ํ…Œ์ŠคํŠธ, ์•„ํ‚คํ…์ฒ˜ ํŒจํ„ด์— ๋Œ€ํ•œ ์ƒ์„ธํ•œ ๋‹ต๋ณ€๊ณผ ์ฝ”๋“œ ์˜ˆ์ œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.