Symfony学习笔记 - 利用Doctrine开发一个学生信息的增删查改
现在在学习Symfony,Doctrine作为官方重点推荐的数库库Bundle,必须要试试!
1、创建Symfony的Demo的Web应用
symfony new symfony_webapp --webapp
正确安装后,目录应该是这样的:
如果你的目录仅仅只有config和vendor目录,那就表明你没有安装完整。原因是symfony时,需要从https://packagist.org下载相应的包,包括Symfony新建应用的模板源码。由于 https://packagist.org限速或者封锁的原因,不能下载完整的包。这时候,你需要配置一下镜像:
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
然后,再执行一遍symfony的创建新应用命令。
2、启动Symfony官方开发的Web Server,根据提示,访问新应用的页面
Web Server的启动命令:
cd symfony_webapp symfony server:start
下图红框标记部分,就是启动的URL地址:
3、创建Student Entity和数据表
- 需要在.env中配置数据库连接,我用的MariaDB:
DATABASE_URL="mysql://root:123456@127.0.0.1:3306/symfony_webapp?serverVersion=10.1.34-MariaDB&charset=utf8mb4"
- 在项目目录下,命令行窗口,运行命令:
php bin/console make:entity
按照提示,输入表名、字段属性(字段名、类型、长度、是否允许为null等)。完了之后,会在src\Entity\
目录下,创建实体类,比如Student、Teacher等。同时,还会在src\Repository\
目录下,创建实体对应的数据存取类,比如StudentRepository,该类除了构造器之外,还有两个默认被注释的方法,findByExampleField
、findOneBySomeField
。
- 在命令行运行生成迁移文件:
php bin/console make:migration
在migrations
目录下,会产生创建表的类文件类,文件名是Version+时间戳,比如Version20250911130132.php。
- 执行所有未应用的迁移,实际修改数据库结构
symfony console doctrine:migrations:migrate
执行完毕后,会在数据库中创建表。
4、实现新增学员
-
创建Student的Controller类
php bin/console make:controller StudentController
执行后,会创建src\Controller\StudentController.php
,或者手工创建也是可以的。 -
在StudentController类中,新增/student/create路由及方法
#[Route('/student/create', name: 'student_create')]
public function createStudent(EntityManagerInterface $entityManager,Request $request): Response
{
$student = new Student();
$form = $this->createForm(StudentType::class, $student);
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->isValid()) {
$entityManager->persist($student);
$entityManager->flush();
$this->addFlash('success', 'Student created successfully.');
return $this->redirectToRoute('student_list');
}
}
return $this->render('student/edit.html.twig', [
'form' => $form,
]);
}
- 创建StudentType,该类是一个form
class StudentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('first_name', TextType::class)
->add('last_name', TextType::class)
->add('birthdate', DateType::class)
->add('description', TextareaType::class)
->add('save', SubmitType::class, ['label' => 'Create']);
}
}
- edit.html.twig的代码如下:
{% extends 'base.html.twig' %}
{% block body %}
{{ form_start(form) }}
<div class="row">
<div class="col-md-6">
{{ form_row(form.firstName) }}
</div>
<div class="col-md-6">
{{ form_row(form.lastName) }}
</div>
</div>
<div class="row">
<div class="col-md-6">
{{ form_row(form.birthdate) }}
</div>
</div>
<div class="row">
<div class="col-md-12">
{{ form_row(form.description) }}
</div>
</div>
{{ form_end(form) }}
{% endblock %}
按照上面4步,就可以完成一个学生的新增了,具体功能包括:
- 新增
- 保存数据
- 保存时,验证数据的有效性验证
5、实现学员列表查询
- 在StudentController中,定义路由和列表查询的方法
#[Route('/students', name: 'student_list')]
public function list(Request $request, EntityManagerInterface $em): Response
{
$initialData = [
'firstName' => $request->query->get('firstName'),
'lastName' => $request->query->get('lastName'),
'birthdate' => $request->query->get('birthdate') ?
\DateTime::createFromFormat('Y-m-d', $request->query->get('birthdate')) : null
];
// 1. 创建查询表单
$form = $this->createForm(StudentSearchType::class,$initialData);
$form->handleRequest($request);
// 2. 初始化查询构建器
$repository = $em->getRepository(Student::class);
$queryBuilder = $repository->createQueryBuilder('s');
$searchData = $form->isSubmitted() ? $form->getData() : $request->query->all();
// 3. 处理表单提交并构建查询条件
if ($form->isSubmitted() && $form->isValid()) {
return $this->redirectToRoute('student_list', [
'firstName' => $form->get('firstName')->getData(),
'lastName' => $form->get('lastName')->getData(),
'birthdate' => $form->get('birthdate')->getData()?->format('Y-m-d'),
]);
}
if (!empty($searchData['firstName'])) {
$queryBuilder->andWhere('s.firstName LIKE :firstName')
->setParameter('firstName', '%' . $searchData['firstName'] . '%');
}
if (!empty($searchData['lastName'])) {
$queryBuilder->andWhere('s.lastName LIKE :lastName')
->setParameter('lastName', '%' . $searchData['lastName'] . '%');
}
if (!empty($searchData['birthdate'])) {
$queryBuilder->andWhere('s.birthdate = :birthdate')
->setParameter('birthdate', $searchData['birthdate']);
}
// 4. 执行查询
$query = $queryBuilder->getQuery();
$students = $query->getResult();
// 5. 渲染模板
return $this->render('student/list.html.twig', [
'form' => $form->createView(),
'students' => $students,
]);
}
2、定义StudentSearchType,这个是列表上方的查询条件
class StudentSearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName', TextType::class, [
'label' => 'First Name',
'required' => false,
])
->add('lastName', TextType::class, [
'label' => 'Last Name',
'required' => false,
])
->add('birthdate', DateType::class, [
'label' => 'Birth Date',
'required' => false,
'widget' => 'single_text',
])
->add('search', SubmitType::class, [
'label' => 'Search',
'attr' => ['class' => 'btn btn-primary'],
]);
;
}
}
- 实现list.html.twig,实现列表中,查询条件和数据行的展示:
{% extends 'base.html.twig' %}
{% block title %}Student List{% endblock %}
{% block body %}
<div class="container mt-4">
<h1>Student Search</h1>
{# 查询表单 #}
{# {{ form_start(form, { attr: { 'data-turbo': 'false' } }) }}#}
{{ form_start(form) }}
<div class="row">
<div class="col-md-3">
{{ form_row(form.firstName) }}
</div>
<div class="col-md-3">
{{ form_row(form.lastName) }}
</div>
<div class="col-md-3">
{{ form_row(form.birthdate) }}
</div>
<div class="col-md-3 align-self-end">
{{ form_row(form.search) }}
<a href="{{ path('student_create') }}"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-edit"></i> create
</a>
</div>
</div>
{{ form_end(form) }}
{# 查询结果列表 #}
<table class="table table-striped mt-4">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Birth Date</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for student in students %}
<tr>
<td>{{ student.firstName }}</td>
<td>{{ student.lastName }}</td>
<td>{{ student.birthdate ? student.birthdate|date('Y-m-d') : '' }}</td>
<td>{{ student.description }}</td>
<td>
{# 编辑按钮 - 链接到编辑路由 #}
<a href="{{ path('student_edit', {id: student.id}) }}"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-edit"></i> Edit
</a>
{# 删除按钮 - 使用表单提交防止CSRF #}
<form method="post"
action="{{ path('student_delete', {id: student.id}) }}"
style="display: inline-block;"
onsubmit="return confirm('Are you sure to delete this student?');">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ student.id) }}">
<button type="submit" class="btn btn-sm btn-outline-danger">
<i class="fas fa-trash"></i> Delete
</button>
</form>
</td>
</tr>
{% else %}
<tr>
<td colspan="4">No records found</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
列表页面,实现的功能有:
- 按条件查询学员,并分行展示
- 新增学员、编辑学员、删除学员的入口
6、编辑学员
在列表中,定义了编辑学员的入口,现在只需要在StudentController中,定义路由并实现编辑的方法:
#[Route('/student/{id}', name: 'student_edit')]
public function edit(EntityManagerInterface $entityManager, StudentRepository $repository,Request $request, Student $student): Response
{
if (!$student) {
throw $this->createNotFoundException(
'No student found for id '.$student->getId()
);
}
$form = $this->createForm(StudentType::class, $student);
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->isValid()) {
$entityManager->persist($student);
$entityManager->flush();
$this->addFlash('success', 'Student created successfully.');
return $this->redirectToRoute('student_list');
}
}
return $this->render('student/edit.html.twig', [
'form' => $form,
]);
}
由于编辑页面与新增页面一样,因此,继续复用edit.html.twig
7. 删除学员
在列表中,定义了编辑学员的入口,现在只需要在StudentController中,定义路由并实现删除的方法:
#[Route('/students/{id}', name: 'student_delete',methods: ['DELETE'])]
public function delete(EntityManagerInterface $entityManager, StudentRepository $repository, Student $student): Response
{
if (!$student) {
throw $this->createNotFoundException(
'No student found for id '.$student->getId()
);
}
$entityManager->remove($student);
$entityManager->flush();
return $this->redirectToRoute('student_list');
}
这样,一个简单的学员管理的增、删、查、改就完成了。
总结
开发这个鉴定的CRUD程序,涉及到的技术包括:
-
Doctrine、Doctrine ORM
a、Entity: 这个也可以
b、Repository:既是数据访问对象(DAO),也是服务(Service)
c、Entity Attribute
d、Entity中的驼峰式(camelCase)属性名,自动转换成SnakeCase的数据库字段名,是 Doctrine 的默认命名策略(Naming Strategy)实现的。 -
MapEntity
处理路由参数实体对象的转换。
Symfony 的 SensioFrameworkExtraBundle 提供的一个功能,通常用于在控制器中自动注入 Doctrine 实体。
Symfony 6.2+ 推荐使用 MapEntity替代 ParamConverter,功能类似但更灵活 -
EntityValueResolver
EntityValueResolver 是底层机制,MapEntity 是其上层封装。可以认为 MapEntity是对 EntityValueResolver的 友好封装,避免直接操作底层解析逻辑。默认情况下,通过Doctrine获取数据库的实体对象,但是脱离Doctrine,自定义实现。 -
Symfony Form
服务端渲染,与twig结合,生成HTML表单,依赖于Validator进行数据验证。如果前后端完全分离,前端采用vue,可以考虑弃用Form。 -
Validator
验证数据的合法性(如字段非空、格式正确、符合业务规则)。完全独立,可直接用于任何数据(如 API、命令行输入)。 -
HttpFundation
是一组 工具函数,用于简化 HTTP 相关操作(如生成响应对象、处理头信息) -
HttpKernel
Symfony 的核心组件,负责 HTTP 请求-响应生命周期的全流程管理,是框架处理 HTTP 请求的中央调度器。他协调处理了Request、Controller、Reponse,完成了HTTP请求的全流程管理。 -
Controller
Symfony 的 Controller 是应用程序的核心协调者,负责处理 HTTP 请求并返回响应,充当 业务逻辑与用户交互的桥梁。Symfony 的 Controller 是 HTTP 请求的入口点,核心作用包括:
a、路由分发:连接 URL 与业务代码。
b、协调服务:调用 Doctrine、Logger 等完成操作。
c、响应生成:输出 HTML/JSON/文件等。
d、安全控制:管理访问权限和数据验证。
它既适合传统 MVC 应用(渲染 HTML),也完美支持 API 开发(返回 JSON),是 Symfony 灵活性的关键设计之一。 -
Dependency Injection
依赖注入 是一种设计模式,通过 外部传递依赖(而非在类内部创建),实现解耦和可测试性。
Symfony 的 DI 系统通过 服务容器(Service Container) 实现:
a、服务(Service):任何可复用的 PHP 对象(如 Doctrine 的 EntityManager、Twig 环境、自定义工具类)。
b、容器:集中管理所有服务的创建和依赖关系,按需实例化并注入。 -
Type Hinting
方法参数中指明类型,让php/Symfony知道该注入什么对象。 -
Service注册
在src或者在composer.json中定义的服务,可以通过autowiring的原理,自动完成注册。
src外或者在composer.json中未定义的服务,或者有些服务需要自定义参数的,可以在config/services.yaml中,手工完成注册。 -
Container
服务容器 是 Symfony 的核心组件,负责 存储、创建和管理所有服务对象(如 EntityManager、Logger、自定义服务等)。它是一个 全局注册表,所有服务都通过容器获取,避免手动实例化。 -
Autowiring
Autowiring 是 Symfony 提供的一种 自动化依赖注入机制,通过 类型提示(Type Hinting) 自动匹配容器中的服务。
它简化了 DI 的配置,无需显式定义每个服务的依赖关系。