./bin/agavi module-create
# Module name: Admin
# 建立登入 Action
./bin/agavi action-wizard
# Module: Admin
# Action: Login
# Views: Input Error Success
<?php
// app/lib/Entity/User.php
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="admin_user")
*/
class User
{
/** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
private $id;
/** @ORM\Column(type="string", length=50, unique=true) */
private $username;
/** @ORM\Column(type="string", length=255) */
private $passwordHash;
/** @ORM\Column(type="string", length=20) */
private $role; // 'dealer' | 'admin'
public function getId(): int { return $this->id; }
public function getUsername(): string { return $this->username; }
public function getRole(): string { return $this->role; }
public function checkPassword(string $plain): bool {
return password_verify($plain, $this->passwordHash);
}
public function setPassword(string $plain): void {
$this->passwordHash = password_hash($plain, PASSWORD_DEFAULT);
}
}
<?php
// app/modules/Admin/actions/LoginAction.class.php
class Admin_LoginAction extends AgaviAction
{
public function execute(AgaviRequestDataHolder $rd)
{
if ($rd->getParameter('login')) {
$username = $rd->getParameter('username');
$password = $rd->getParameter('password');
$em = DoctrineHelper::getEntityManager();
$user = $em->getRepository('User')->findOneBy(['username' => $username]);
if ($user && $user->checkPassword($password)) {
// 寫入 Session
$this->getContext()->getSession()->setAttribute('admin_user', [
'id' => $user->getId(),
'username' => $user->getUsername(),
'role' => $user->getRole(),
]);
return 'Success';
}
$this->setAttribute('error', '帳號或密碼錯誤');
return 'Error';
}
return 'Input';
}
public function validate(AgaviValidator $validator)
{
$validator->addRule('username', new AgaviRequiredValidator());
$validator->addRule('password', new AgaviRequiredValidator());
}
}
<?php
// app/modules/Admin/filters/AuthFilter.class.php
class Admin_AuthFilter extends AgaviFilter
{
public function execute(AgaviFilterChain $chain)
{
$session = $this->getContext()->getSession();
$user = $session->getAttribute('admin_user');
// 登入頁、靜態資源放行
$actionName = $this->getContext()->getController()->getActionName();
if (in_array($actionName, ['Login', 'Logout'])) {
return $chain->execute();
}
if (!$user) {
// 未登入 → 導向登入頁
$this->getContext()->getController()->redirect('admin.login');
return;
}
// 可選:角色授權檢查
$requiredRole = $this->getParameter('required_role');
if ($requiredRole && $user['role'] !== $requiredRole) {
throw new AgaviSecurityException('權限不足');
}
return $chain->execute();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<ae:configurations xmlns:ae="https://agavi.org/agavi/config/global/envelope/1.0"
xmlns="https://agavi.org/agavi/config/parts/filter/1.0">
<ae:configuration>
<filters>
<filter name="auth" class="Admin_AuthFilter">
<parameters>
<parameter name="required_role">admin</parameter>
</parameters>
</filter>
</filters>
<filter-mappings>
<filter-mapping filter="auth" pattern="^admin\." />
</filter-mappings>
</ae:configuration>
</ae:configurations>
<?php // app/templates/AdminMaster.php ?>
<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<title><?= $t->getAttribute('_title', 'Admin') ?> - WASP Admin</title>
<link rel="stylesheet" href="<?= $ro->getBaseHref() ?>/css/admin.css">
</head><body>
<header>
<h1><img src="<?= $ro->getBaseHref() ?>/images/logo-admin.png" alt="WASP Admin"></h1>
<nav><a href="<?= $ro->gen('admin.dashboard') ?>">儀表板</a> | <a href="<?= $ro->gen('admin.logout') ?>">登出</a></nav>
</header>
<main><?= $inner ?></main>
<footer>Powered by Agavi</footer>
</body></html>
<?php
// app/modules/Admin/views/DashboardSuccessView.class.php
class Admin_DashboardSuccessView extends AgaviHtmlView
{
public function setupHtml()
{
$this->setLayout('AdminMaster'); // 關鍵:指定後台母版
}
}
| Action | HTTP Method | View | 關鍵邏輯 |
|---|---|---|---|
| CreateAction | GET/POST | Input/Success | POST 時驗證 → 建立 Entity → persist → flush → 導向列表 |
| EditAction | GET/POST | Input/Success | GET 讀取 Entity 填充表單;POST 更新欄位 → flush |
| DeleteAction | POST | Success | CSRF 驗證 → find Entity → remove → flush → JSON 回應 (AJAX) |
下一篇將實作 **Create/Edit 表單的詳細驗證、檔案上傳 (車輛圖片)、列表分頁排序、搜尋功能**,並示範如何輸出 **JSON/XML/RSS** 多格式 API。