下面把前面的概念串成一条完整的政务流。
场景:
- 市民访问
/permits/new - 系统渲染申请表
- 提交后自动映射到
BusinessPermit - 表单校验通过
PermitService负责落库并投递异步消息- 后台专办员继续做通知与后续流程
- 最后重定向到详情页
先看总流转:
GET /permits/new
|
v
Controller 创建 Form
|
v
Twig 渲染申请页
|
v
POST /permits/new
|
v
handleRequest() 回填对象
|
v
Validator 校验
|
+--> 不通过:原页回显错误
|
+--> 通过
|
v
PermitService
|
+--> Doctrine flush()
|
+--> Messenger dispatch()
|
v
redirectToRoute('permit_show')核心代码如下。
#1. 实体:档案对象
<?php
namespace App\Entity;
use App\Repository\BusinessPermitRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: BusinessPermitRepository::class)]
class BusinessPermit
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 120)]
#[Assert\NotBlank]
private string $applicantName = '';
#[ORM\Column(length: 160)]
#[Assert\NotBlank]
private string $businessName = '';
#[ORM\Column(type: 'text', nullable: true)]
private ?string $description = null;
#[ORM\Column(length: 20)]
private string $status = 'draft';
#[ORM\Column(type: 'datetime_immutable')]
private \DateTimeImmutable $createdAt;
public function __construct()
{
$this->createdAt = new \DateTimeImmutable();
}
public function markSubmitted(): void
{
$this->status = 'submitted';
}
public function getId(): ?int
{
return $this->id;
}
public function getBusinessName(): string
{
return $this->businessName;
}
public function getStatus(): string
{
return $this->status;
}
}#2. 表单:申报表定义
<?php
namespace App\Form;
use App\Entity\BusinessPermit;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class BusinessPermitType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('applicantName', TextType::class, ['label' => '申请人'])
->add('businessName', TextType::class, ['label' => '商户名称'])
->add('description', TextareaType::class, [
'label' => '经营说明',
'required' => false,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => BusinessPermit::class,
]);
}
}#3. 服务:真正的办事处室
<?php
namespace App\Service;
use App\Entity\BusinessPermit;
use App\Message\PermitSubmittedMessage;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\MessageBusInterface;
final class PermitService
{
public function __construct(
private EntityManagerInterface $entityManager,
private MessageBusInterface $bus,
) {
}
public function submit(BusinessPermit $permit): void
{
$permit->markSubmitted();
$this->entityManager->persist($permit);
$this->entityManager->flush();
$this->bus->dispatch(new PermitSubmittedMessage($permit->getId()));
}
}#4. 控制器:窗口只做协调
<?php
namespace App\Controller;
use App\Entity\BusinessPermit;
use App\Form\BusinessPermitType;
use App\Service\PermitService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class PermitController extends AbstractController
{
#[Route('/permits/new', name: 'permit_new', methods: ['GET', 'POST'])]
public function new(Request $request, PermitService $permitService): Response
{
$permit = new BusinessPermit();
$form = $this->createForm(BusinessPermitType::class, $permit);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$permitService->submit($permit);
return $this->redirectToRoute('permit_show', [
'id' => $permit->getId(),
]);
}
return $this->render('permit/new.html.twig', [
'form' => $form,
]);
}
#[Route('/permits/{id}', name: 'permit_show', methods: ['GET'])]
public function show(BusinessPermit $permit): Response
{
return $this->render('permit/show.html.twig', [
'permit' => $permit,
]);
}
}#5. 消息与处理器:后台专办员继续干活
<?php
namespace App\Message;
final readonly class PermitSubmittedMessage
{
public function __construct(public int $permitId)
{
}
}<?php
namespace App\MessageHandler;
use App\Message\PermitSubmittedMessage;
use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
final class PermitSubmittedMessageHandler
{
public function __construct(
private LoggerInterface $logger,
) {
}
public function __invoke(PermitSubmittedMessage $message): void
{
$this->logger->info('营业许可申请已进入后台处理', [
'permit_id' => $message->permitId,
]);
}
}#6. 模板:给人看的公告页
{# templates/permit/new.html.twig #}
<h1>营业许可申请</h1>
{{ form_start(form) }}
{{ form_row(form.applicantName) }}
{{ form_row(form.businessName) }}
{{ form_row(form.description) }}
<button type="submit">提交申请</button>
{{ form_end(form) }}{# templates/permit/show.html.twig #}
<h1>{{ permit.businessName }}</h1>
<p>当前状态:{{ permit.status }}</p>这套例子最重要的不是语法,而是结构:
表单
-> 控制器
-> 服务
-> Doctrine
-> Messenger
-> Twig
每一层都像政务城区里职责清晰的楼
而不是把所有事堆在一个“超级窗口”里