Symfony学习笔记 - 利用Doctrine开发一个学生信息的增删查改

现在在学习Symfony,Doctrine作为官方重点推荐的数库库Bundle,必须要试试!

1、创建Symfony的Demo的Web应用

symfony new symfony_webapp --webapp
正确安装后,目录应该是这样的:
image
如果你的目录仅仅只有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地址:
image

3、创建Student Entity和数据表

  1. 需要在.env中配置数据库连接,我用的MariaDB:
DATABASE_URL="mysql://root:123456@127.0.0.1:3306/symfony_webapp?serverVersion=10.1.34-MariaDB&charset=utf8mb4"
  1. 在项目目录下,命令行窗口,运行命令:
php bin/console make:entity

按照提示,输入表名、字段属性(字段名、类型、长度、是否允许为null等)。完了之后,会在src\Entity\目录下,创建实体类,比如Student、Teacher等。同时,还会在src\Repository\目录下,创建实体对应的数据存取类,比如StudentRepository,该类除了构造器之外,还有两个默认被注释的方法,findByExampleFieldfindOneBySomeField

  1. 在命令行运行生成迁移文件:
php bin/console make:migration

migrations目录下,会产生创建表的类文件类,文件名是Version+时间戳,比如Version20250911130132.php。

  1. 执行​​所有未应用的迁移,实际修改数据库结构
symfony console doctrine:migrations:migrate

执行完毕后,会在数据库中创建表。

4、实现新增学员

  1. 创建Student的Controller类
    php bin/console make:controller StudentController
    执行后,会创建src\Controller\StudentController.php,或者手工创建也是可以的。

  2. 在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,
    ]);
}
  1. 创建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']);
    }
}
  1. 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步,就可以完成一个学生的新增了,具体功能包括:

  • 新增
  • 保存数据
  • 保存时,验证数据的有效性验证
    image

5、实现学员列表查询

  1. 在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'],
            ]);
        ;
    }
}
  1. 实现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 %}

列表页面,实现的功能有:

  1. 按条件查询学员,并分行展示
  2. 新增学员、编辑学员、删除学员的入口
    image

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
image

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程序,涉及到的技术包括:

  1. Doctrine、Doctrine ORM
    a、Entity: 这个也可以
    b、Repository:既是数据访问对象(DAO),也是服务(Service)
    c、Entity Attribute
    d、Entity中的驼峰式(camelCase)属性名,自动转换成SnakeCase的数据库字段名,是 Doctrine 的默认命名策略(Naming Strategy)实现的。

  2. MapEntity
    处理路由参数实体对象的转换。
    ​Symfony 的 SensioFrameworkExtraBundle​​ 提供的一个功能,通常用于在控制器中自动注入 Doctrine 实体。
    Symfony 6.2+ 推荐使用 MapEntity替代 ParamConverter,功能类似但更灵活

  3. EntityValueResolver
    EntityValueResolver 是底层机制,MapEntity 是其上层封装​。可以认为 MapEntity是对 EntityValueResolver的 ​​友好封装​​,避免直接操作底层解析逻辑。默认情况下,通过Doctrine获取数据库的实体对象,但是脱离Doctrine,自定义实现。

  4. Symfony Form​
    服务端渲染,与twig结合,生成HTML表单,依赖于Validator进行数据验证。如果前后端完全分离,前端采用vue,可以考虑弃用Form。

  5. Validator
    验证数据的合法性(如字段非空、格式正确、符合业务规则)。完全独立,可直接用于任何数据(如 API、命令行输入)。

  6. HttpFundation
    是一组 ​​工具函数​​,用于简化 HTTP 相关操作(如生成响应对象、处理头信息)

  7. HttpKernel
    Symfony 的核心组件,负责 ​​HTTP 请求-响应生命周期的全流程管理​​,是框架处理 HTTP 请求的中央调度器。他协调处理了Request、Controller、Reponse,完成了HTTP请求的全流程管理。

  8. Controller
    Symfony 的 ​​Controller​​ 是应用程序的核心协调者,负责处理 HTTP 请求并返回响应,充当 ​​业务逻辑与用户交互的桥梁。Symfony 的 Controller 是 ​​HTTP 请求的入口点​​,核心作用包括:
    a、​​路由分发​​:连接 URL 与业务代码。
    b、​​协调服务​​:调用 Doctrine、Logger 等完成操作。
    c、响应生成​​:输出 HTML/JSON/文件等。
    d、安全控制​​:管理访问权限和数据验证。
    它既适合传统 MVC 应用(渲染 HTML),也完美支持 API 开发(返回 JSON),是 Symfony 灵活性的关键设计之一。

  9. Dependency Injection
    依赖注入​​ 是一种设计模式,通过 ​​外部传递依赖​​(而非在类内部创建),实现解耦和可测试性。
    Symfony 的 DI 系统通过 ​​服务容器(Service Container)​​ 实现:
    a、​​服务(Service)​​:任何可复用的 PHP 对象(如 Doctrine 的 EntityManager、Twig 环境、自定义工具类)。
    b、容器​​:集中管理所有服务的创建和依赖关系,按需实例化并注入。

  10. Type Hinting
    方法参数中指明类型,让php/Symfony知道该注入什么对象。

  11. Service注册
    在src或者在composer.json中定义的服务,可以通过autowiring的原理,自动完成注册。
    src外或者在composer.json中未定义的服务,或者有些服务需要自定义参数的,可以在config/services.yaml中,手工完成注册。

  12. Container
    服务容器​​ 是 Symfony 的核心组件,负责 ​​存储、创建和管理所有服务对象​​(如 EntityManager、Logger、自定义服务等)。它是一个 ​​全局注册表​​,所有服务都通过容器获取,避免手动实例化。

  13. Autowiring
    Autowiring​​ 是 Symfony 提供的一种 ​​自动化依赖注入机制​​,通过 ​​类型提示(Type Hinting)​​ 自动匹配容器中的服务。
    它简化了 DI 的配置,无需显式定义每个服务的依赖关系。

posted @ 2025-09-16 14:20  繁星灼灼  阅读(4)  评论(0)    收藏  举报