第一个应用 #

一、创建控制器 #

1.1 使用Maker Bundle #

bash
# 创建控制器
php bin/console make:controller HomeController

# 输出
created: src/Controller/HomeController.php
created: templates/home/index.html.twig

1.2 控制器代码 #

php
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class HomeController extends AbstractController
{
    #[Route('/home', name: 'app_home')]
    public function index(): Response
    {
        return $this->render('home/index.html.twig', [
            'controller_name' => 'HomeController',
        ]);
    }
}

1.3 控制器解析 #

text
┌─────────────────────────────────────────────────────┐
│                  控制器结构解析                      │
├─────────────────────────────────────────────────────┤
│                                                     │
│  #[Route('/home', name: 'app_home')]               │
│       │              │                              │
│       │              └── 路由名称                   │
│       └── URL路径                                   │
│                                                     │
│  public function index(): Response                 │
│       │              │                              │
│       │              └── 返回类型                   │
│       └── 动作方法名                                │
│                                                     │
│  return $this->render(...)                         │
│       │              │                              │
│       │              └── 模板变量                   │
│       └── 渲染模板                                  │
│                                                     │
└─────────────────────────────────────────────────────┘

二、路由配置 #

2.1 注解路由 #

php
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
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');
    }

    #[Route('/about', name: 'app_about')]
    public function about(): Response
    {
        return new Response('About Page');
    }

    #[Route('/contact', name: 'app_contact', methods: ['GET', 'POST'])]
    public function contact(): Response
    {
        return new Response('Contact Page');
    }
}

2.2 动态路由参数 #

php
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class UserController extends AbstractController
{
    #[Route('/user/{id}', name: 'app_user_show', requirements: ['id' => '\d+'])]
    public function show(int $id): Response
    {
        return new Response("User ID: $id");
    }

    #[Route('/blog/{slug}', name: 'app_blog_show')]
    public function blogShow(string $slug): Response
    {
        return new Response("Blog Slug: $slug");
    }

    #[Route('/category/{category}/post/{post}', name: 'app_post_show')]
    public function postShow(string $category, string $post): Response
    {
        return new Response("Category: $category, Post: $post");
    }
}

2.3 路由默认值 #

php
<?php

#[Route('/page/{page}', name: 'app_page', defaults: ['page' => 1])]
public function page(int $page): Response
{
    return new Response("Page: $page");
}

#[Route('/article/{id}/{slug}', name: 'app_article', defaults: ['slug' => null])]
public function article(int $id, ?string $slug): Response
{
    return new Response("Article ID: $id, Slug: $slug");
}

2.4 路由条件 #

php
<?php

use Symfony\Component\ExpressionLanguage\Expression;

#[Route(
    '/admin',
    name: 'app_admin',
    condition: "request.headers.get('X-Admin') === 'true'"
)]
public function admin(): Response
{
    return new Response('Admin Page');
}

#[Route(
    '/api/{version}',
    name: 'app_api',
    requirements: ['version' => 'v1|v2']
)]
public function api(string $version): Response
{
    return new Response("API Version: $version");
}

三、创建模板 #

3.1 基础模板 #

twig
{# templates/base.html.twig #}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Symfony App{% endblock %}</title>
    <link rel="stylesheet" href="{{ asset('css/style.css') }}">
    {% block stylesheets %}{% endblock %}
</head>
<body>
    <nav class="navbar">
        <a href="{{ path('app_home') }}">首页</a>
        <a href="{{ path('app_about') }}">关于</a>
        <a href="{{ path('app_contact') }}">联系</a>
    </nav>

    <main class="container">
        {% block body %}{% endblock %}
    </main>

    <footer>
        <p>&copy; {{ "now"|date("Y") }} Symfony App</p>
    </footer>

    <script src="{{ asset('js/app.js') }}"></script>
    {% block javascripts %}{% endblock %}
</body>
</html>

3.2 继承模板 #

twig
{# templates/home/index.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}首页 - {{ parent() }}{% endblock %}

{% block body %}
<div class="hero">
    <h1>欢迎来到 Symfony</h1>
    <p>这是一个强大的 PHP 企业级框架</p>
</div>

<div class="features">
    <h2>核心特性</h2>
    <ul>
        <li>高性能</li>
        <li>模块化</li>
        <li>可扩展</li>
        <li>企业级</li>
    </ul>
</div>
{% endblock %}

3.3 模板变量 #

php
<?php

#[Route('/user/profile', name: 'app_user_profile')]
public function profile(): Response
{
    $user = [
        'name' => '张三',
        'email' => 'zhangsan@example.com',
        'age' => 25,
        'skills' => ['PHP', 'Symfony', 'MySQL'],
        'active' => true,
    ];

    return $this->render('user/profile.html.twig', [
        'user' => $user,
        'title' => '用户资料',
    ]);
}
twig
{# templates/user/profile.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}{{ title }} - {{ parent() }}{% endblock %}

{% block body %}
<h1>{{ title }}</h1>

<div class="user-profile">
    <p>姓名: {{ user.name }}</p>
    <p>邮箱: {{ user.email }}</p>
    <p>年龄: {{ user.age }}</p>
    
    <h3>技能</h3>
    <ul>
        {% for skill in user.skills %}
            <li>{{ skill }}</li>
        {% endfor %}
    </ul>
    
    <p>状态: {{ user.active ? '活跃' : '禁用' }}</p>
</div>
{% endblock %}

四、请求与响应 #

4.1 获取请求参数 #

php
<?php

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

#[Route('/search', name: 'app_search')]
public function search(Request $request): Response
{
    $keyword = $request->query->get('keyword', '');
    $page = $request->query->getInt('page', 1);
    
    return new Response("搜索: $keyword, 页码: $page");
}

#[Route('/user/create', name: 'app_user_create', methods: ['POST'])]
public function create(Request $request): Response
{
    $name = $request->request->get('name');
    $email = $request->request->get('email');
    
    return new Response("创建用户: $name, $email");
}

4.2 获取路由参数 #

php
<?php

#[Route('/product/{id}/review/{reviewId}', name: 'app_product_review')]
public function review(int $id, int $reviewId): Response
{
    return new Response("产品ID: $id, 评论ID: $reviewId");
}

#[Route('/article/{id}', name: 'app_article_show')]
public function articleShow(Request $request, int $id): Response
{
    $routeName = $request->attributes->get('_route');
    $routeParams = $request->attributes->get('_route_params');
    
    return new Response("文章ID: $id, 路由: $routeName");
}

4.3 JSON响应 #

php
<?php

use Symfony\Component\HttpFoundation\JsonResponse;

#[Route('/api/users', name: 'api_users')]
public function users(): JsonResponse
{
    $users = [
        ['id' => 1, 'name' => '张三'],
        ['id' => 2, 'name' => '李四'],
        ['id' => 3, 'name' => '王五'],
    ];
    
    return $this->json($users);
}

#[Route('/api/user/{id}', name: 'api_user_show')]
public function userShow(int $id): JsonResponse
{
    return $this->json([
        'id' => $id,
        'name' => '用户' . $id,
        'status' => 'success',
    ], 200, ['X-Custom-Header' => 'value']);
}

4.4 重定向 #

php
<?php

#[Route('/redirect-home', name: 'app_redirect_home')]
public function redirectHome(): Response
{
    return $this->redirectToRoute('app_home');
}

#[Route('/redirect-user/{id}', name: 'app_redirect_user')]
public function redirectUser(int $id): Response
{
    return $this->redirectToRoute('app_user_show', ['id' => $id]);
}

#[Route('/redirect-external', name: 'app_redirect_external')]
public function redirectExternal(): Response
{
    return $this->redirect('https://symfony.com');
}

五、调试工具 #

5.1 调试工具栏 #

Symfony提供了强大的调试工具栏:

text
┌─────────────────────────────────────────────────────┐
│ [Symfony] [200 OK] [Time: 45ms] [Memory: 12MB]     │
│ [Request] [Security] [Forms] [Doctrine] [Twig]     │
│ [Logs] [Events] [Router] [Cache] [Profiler]        │
└─────────────────────────────────────────────────────┘

5.2 Dump调试 #

php
<?php

use Symfony\Component\VarDumper\VarDumper;

#[Route('/debug', name: 'app_debug')]
public function debug(): Response
{
    $data = ['name' => '张三', 'age' => 25];
    
    dump($data);
    dd($data);
    
    return new Response('Debug');
}

5.3 查看路由 #

bash
# 查看所有路由
php bin/console debug:router

# 查看特定路由
php bin/console debug:router app_home

# 输出示例
+--------------+-----------+------------+-----------------------------+
| Name         | Method    | Scheme     | Path                        |
+--------------+-----------+------------+-----------------------------+
| app_home     | ANY       | ANY        | /                           |
| app_about    | ANY       | ANY        | /about                      |
| app_user_show| ANY       | ANY        | /user/{id}                  |
+--------------+-----------+------------+-----------------------------+

5.4 查看服务 #

bash
# 查看所有服务
php bin/console debug:container

# 查看特定服务
php bin/console debug:container twig

# 查看自动装配
php bin/console debug:autowiring

六、完整示例 #

6.1 创建博客列表页面 #

控制器:

php
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class BlogController extends AbstractController
{
    private array $posts = [
        1 => ['id' => 1, 'title' => 'Symfony入门', 'content' => '学习Symfony基础'],
        2 => ['id' => 2, 'title' => 'Doctrine ORM', 'content' => '数据库操作'],
        3 => ['id' => 3, 'title' => 'Twig模板', 'content' => '模板引擎使用'],
    ];

    #[Route('/blog', name: 'app_blog_index')]
    public function index(): Response
    {
        return $this->render('blog/index.html.twig', [
            'posts' => $this->posts,
        ]);
    }

    #[Route('/blog/{id}', name: 'app_blog_show', requirements: ['id' => '\d+'])]
    public function show(int $id): Response
    {
        if (!isset($this->posts[$id])) {
            throw $this->createNotFoundException('文章不存在');
        }

        return $this->render('blog/show.html.twig', [
            'post' => $this->posts[$id],
        ]);
    }
}

列表模板:

twig
{# templates/blog/index.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}博客列表{% endblock %}

{% block body %}
<h1>博客文章</h1>

<div class="post-list">
    {% for post in posts %}
    <article class="post">
        <h2>
            <a href="{{ path('app_blog_show', {id: post.id}) }}">
                {{ post.title }}
            </a>
        </h2>
        <p>{{ post.content }}</p>
    </article>
    {% else %}
    <p>暂无文章</p>
    {% endfor %}
</div>
{% endblock %}

详情模板:

twig
{# templates/blog/show.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}{{ post.title }}{% endblock %}

{% block body %}
<article class="post-detail">
    <h1>{{ post.title }}</h1>
    <div class="content">
        {{ post.content }}
    </div>
    
    <a href="{{ path('app_blog_index') }}">返回列表</a>
</article>
{% endblock %}

七、运行测试 #

7.1 创建测试 #

php
<?php

namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class BlogControllerTest extends WebTestCase
{
    public function testIndex(): void
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/blog');

        $this->assertResponseIsSuccessful();
        $this->assertSelectorTextContains('h1', '博客文章');
    }

    public function testShow(): void
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/blog/1');

        $this->assertResponseIsSuccessful();
        $this->assertSelectorTextContains('h1', 'Symfony入门');
    }
}

7.2 运行测试 #

bash
# 运行所有测试
php bin/phpunit

# 运行特定测试文件
php bin/phpunit tests/Controller/BlogControllerTest.php

# 运行特定测试方法
php bin/phpunit --filter testIndex

八、总结 #

本章学习了:

  • 创建控制器
  • 配置路由
  • 创建Twig模板
  • 请求参数获取
  • 响应类型
  • 重定向操作
  • 调试工具使用
  • 完整博客示例

下一章将学习 路由基础

最后更新:2026-03-28