Symfony 1.x (舊版) 採用 **模組-動作-模板** 架構,雖已停止維護,但其設計思想 (MVC、Routing、Helper、CSRF) 深刻影響後續 Symfony 2+、Laravel 等現代框架。本文系統整理 Symfony 1.x 頁面建立流程:模組初始化、Action/Template 命名慣例、Routing 解耦 URL、Helper 簡化視圖、表單/鏈接生成、請求參數存取,並對應現代 Symfony 寫法對照。
# Symfony 1.x 專案根目錄 php symfony init-module frontend article # 產生目錄結構: # apps/frontend/modules/article/ # actions/actions.class.php # templates/ # config/ # lib/ # validate/
// apps/frontend/modules/article/actions/actions.class.php
class articleActions extends sfActions
{
public function executeIndex(sfWebRequest $request)
{
// 業務邏輯:從資料庫撈取文章列表
$this->articles = ArticlePeer::doSelect(new Criteria()); // Propel 1.x
// 或 Doctrine: $this->articles = Doctrine::getTable('Article')->findAll();
// 設定頁面標題供 layout 使用
$this->getResponse()->setTitle('文章列表');
}
public function executeShow(sfWebRequest $request)
{
$id = $request->getParameter('id');
$this->article = ArticlePeer::retrieveByPk($id);
$this->forward404Unless($this->article);
}
public function executeNew(sfWebRequest $request)
{
$this->form = new ArticleForm();
}
public function executeCreate(sfWebRequest $request)
{
$this->form = new ArticleForm();
$this->processForm($request, $this->form);
$this->setTemplate('new'); // 失敗時顯示 newSuccess.php
}
protected function processForm(sfWebRequest $request, sfForm $form)
{
$form->bind($request->getParameter($form->getName()));
if ($form->isValid()) {
$article = $form->save();
$this->redirect('article/show?id=' . $article->getId());
}
}
}
文章列表
| 標題 | 發布時間 | 操作 |
|---|---|---|
| getTitle(), 'article/show?id='.$article->getId()) ?> | getCreatedAt(), 'f') ?> | getId()) ?> getId(), 'method=delete confirm=確定刪除?') ?> |
編輯 `apps/frontend/config/routing.yml`:
article:
url: /articles
param: { module: article, action: index }
article_show:
url: /articles/:id
param: { module: article, action: show }
requirements: { id: \d+ }
article_new:
url: /articles/new
param: { module: article, action: new }
article_edit:
url: /articles/:id/edit
param: { module: article, action: edit }
requirements: { id: \d+ }
article_delete:
url: /articles/:id
param: { module: article, action: delete }
requirements: { id: \d+ }
method: [delete]
# 首頁
homepage:
url: /
param: { module: article, action: index }
現在 URL 變為美觀的 `/articles/123`,且 Action 程式碼完全不用修改。
| Helper | 用途 | 範例 |
|---|---|---|
| link_to() | 生成 連結 | |
| url_for() | 產生絕對/相對 URL | |
| form_tag() | 開啟 | |
| input_tag() | getTitle()) ?> | |
| textarea_tag() | getBody()) ?> | |
| select_tag() | getCategoryId())) ?> | |
| submit_tag() | ||
| form_remote_tag() | AJAX 表單 | 'article/create', 'update' => 'result')) ?> |
// lib/form/ArticleForm.class.php
class ArticleForm extends BaseForm
{
public function configure()
{
$this->setWidgets(array(
'title' => new sfWidgetFormInputText(),
'body' => new sfWidgetFormTextarea(),
'category_id' => new sfWidgetFormDoctrineChoice(array('model' => 'Category')),
));
$this->setValidators(array(
'title' => new sfValidatorString(array('required' => true, 'max_length' => 255)),
'body' => new sfValidatorString(array('required' => true)),
'category_id' => new sfValidatorDoctrineChoice(array('model' => 'Category')),
));
$this->widgetSchema->setNameFormat('article[%s]');
// CSRF 保護自動啟用 (BaseForm 預設)
}
}
renderHiddenFields() ?> renderGlobalErrors() ?>renderLabel() ?> render() ?> renderError() ?>renderLabel() ?> render() ?> renderError() ?>renderLabel() ?> render() ?>
// Action 內
$id = $request->getParameter('id'); // 單一參數
$all = $request->getParameters(); // 所有參數陣列
$has = $request->hasParameter('keyword'); // 是否存在
// Template 內 (sf_request 變數)
$id = $sf_request->getParameter('id');
$keyword = $sf_request->getParameter('keyword', '預設值');
| Symfony 1.x | Modern Symfony (Controller) | 關鍵差異 |
|---|---|---|
| 模組/Action | Controller 類別 + 方法 | 單一類別可包含多端點 |
| actions.class.php | src/Controller/ArticleController.php | |
| executeIndex() | public function index(): Response | 回傳 Response 物件 |
| routing.yml | config/routes.yaml / @Route 註解 | 屬性路由更直觀 |
| Template .php | templates/article/index.html.twig | Twig 模板引擎 |
| Helper link_to() | {{ path('article_show', {id: 1}) }} | Twig 函數 |
| sfForm | Symfony Form Component | 獨立元件,支援資料轉換器 |
| sfWebRequest | Request $request (注入) | PSR-7 相容 |
| $this->redirect() | return $this->redirectToRoute('article_show', ['id' => 1]) | |
| Propel/Doctrine 1 | Doctrine ORM 2 | Entity + Repository |
// src/Controller/ArticleController.php
namespace App\Controller;
use App\Entity\Article;
use App\Form\ArticleType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class ArticleController extends AbstractController
{
#[Route('/articles', name: 'article_index', methods: ['GET'])]
public function index(EntityManagerInterface $em): Response
{
$articles = $em->getRepository(Article::class)->findBy([], ['createdAt' => 'DESC']);
return $this->render('article/index.html.twig', ['articles' => $articles]);
}
#[Route('/articles/{id}', name: 'article_show', requirements: ['id' => '\d+'], methods: ['GET'])]
public function show(Article $article): Response
{
return $this->render('article/show.html.twig', ['article' => $article]);
}
#[Route('/articles/new', name: 'article_new', methods: ['GET', 'POST'])]
public function new(Request $request, EntityManagerInterface $em): Response
{
$article = new Article();
$form = $this->createForm(ArticleType::class, $article);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($article);
$em->flush();
$this->addFlash('success', '文章已建立');
return $this->redirectToRoute('article_show', ['id' => $article->getId()]);
}
return $this->render('article/new.html.twig', ['form' => $form->createView()]);
}
}