Loading...
2025-11-12 11:25
16

Laminas 是 Zend Framework 的延續與社群化版本,維持企業級的穩定與可擴充性。它提供兩條建站路線:傳統 MVC(laminas-mvc)中介層/函式管線式(Mezzio)。本教學用「能上線」為目標,帶你從 0 建立專案、路由、控制器、視圖、DI 工廠與資料庫,最後補充表單驗證與 Mezzio 實戰。

一、安裝與專案骨架

MVC 骨架安裝:

composer create-project laminas/laminas-mvc-skeleton myapp cd myapp php -S 0.0.0.0:8080 -t public

Mezzio 骨架安裝:(若你想用中介層架構)

composer create-project mezzio/mezzio-skeleton myapi cd myapi php -S 0.0.0.0:8080 -t public

二、專案結構速覽(MVC)

  • module/:功能模組(每個模組自帶 configsrcview)。
  • config/:全域設定(autoload/*.global.php*.local.php)。
  • public/:前端入口(index.php)。
  • vendor/:Composer 套件。

三、建立第一個模組與路由(MVC)

1) 建立模組目錄:

mkdir -p module/Blog/{config,src/Controller,view/blog} touch module/Blog/Module.php touch module/Blog/config/module.config.php

2) 註冊模組:在 config/modules.config.php 加入 'Blog'

3) 設定路由與控制器(module/Blog/config/module.config.php):

<?php use Laminas\Router\Http\Segment; use Blog\Controller\PostController;

return [
'router' => [
'routes' => [
'blog' => [
'type' => Segment::class,
'options' => [
'route' => '/blog[/:id]',
'defaults' => [
'controller' => PostController::class,
'action' => 'index',
],
],
],
],
],
'controllers' => [
'factories' => [
PostController::class => Blog\Factory\PostControllerFactory::class,
],
],
'view_manager' => [
'template_path_stack' => [ DIR . '/../view' ],
],
];

四、控制器與 DI 工廠(MVC)

Laminas 採用 DI 工廠模式(Factory)建立物件,方便注入服務。

1) 控制器(module/Blog/src/Controller/PostController.php):

<?php namespace Blog\Controller;

use Laminas\Mvc\Controller\AbstractActionController;
use Laminas\View\Model\ViewModel;
use Blog\Service\PostService;

class PostController extends AbstractActionController
{
public function __construct(private PostService $service) {}

public function indexAction()
{
    $id = $this->params()->fromRoute('id');
    $posts = $id ? [$this->service->find($id)] : $this->service->list();
    return new ViewModel(['posts' => $posts]);
}


}

2) 工廠(module/Blog/src/Factory/PostControllerFactory.php):

<?php namespace Blog\Factory;

use Psr\Container\ContainerInterface;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Blog\Controller\PostController;
use Blog\Service\PostService;

class PostControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $c, $requestedName, array $options = null)
{
return new PostController($c->get(PostService::class));
}
}

3) 服務註冊(module/Blog/config/module.config.php 補充):

return [ // ... 'service_manager' => [ 'factories' => [ Blog\Service\PostService::class => Blog\Factory\PostServiceFactory::class, ], ], ];

五、服務層與資料庫(TableGateway / PDO)(MVC)

安裝 laminas/laminas-db

composer require laminas/laminas-db

1) 建立服務(module/Blog/src/Service/PostService.php):

<?php namespace Blog\Service;

use Laminas\Db\TableGateway\TableGatewayInterface;

class PostService
{
public function __construct(private TableGatewayInterface $table) {}

public function list(): array
{
    return $this->table->select()->toArray();
}

public function find(int $id): array|null
{
    $row = $this->table->select(['id' => $id])->current();
    return $row ? (array) $row : null;
}


}

2) 服務工廠(module/Blog/src/Factory/PostServiceFactory.php):

<?php namespace Blog\Factory;

use Psr\Container\ContainerInterface;
use Laminas\Db\Adapter\Adapter;
use Laminas\Db\TableGateway\TableGateway;
use Blog\Service\PostService;

class PostServiceFactory
{
public function __invoke(ContainerInterface $c): PostService
{
$adapter = $c->get(Adapter::class);
$table = new TableGateway('posts', $adapter);
return new PostService($table);
}
}

3) 設定資料庫連線(config/autoload/global.php / local.php):

<?php return [ 'db' => [ 'driver' => 'Pdo_Mysql', 'hostname' => '127.0.0.1', 'database' => 'myapp', 'username' => 'root', 'password' => 'secret', 'charset' => 'utf8mb4', ], 'service_manager' => [ 'factories' => [ Laminas\Db\Adapter\Adapter::class => Laminas\Db\Adapter\AdapterServiceFactory::class, ], ], ];

六、視圖與模板(MVC)

建立視圖檔(module/Blog/view/blog/post/index.phtml):

<h2>Blog Posts</h2> <ul> <?php foreach ($this->posts as $post): ?> <li> <strong><?= $this->escapeHtml($post['title']) ?></strong> <div><?= $this->escapeHtml($post['excerpt']) ?></div> </li> <?php endforeach; ?> </ul>

七、表單與驗證(InputFilter)

  • 安裝:composer require laminas/laminas-form laminas/laminas-inputfilter laminas/laminas-validator
  • 在 Service/Controller 中組合 InputFilter,於 POST 前先做 setData()isValid()
<?php use Laminas\InputFilter\InputFilter;

$filter = new InputFilter();
$filter->add(['name' => 'title', 'required' => true, 'filters' => [['name' => 'StringTrim']], 'validators' => [['name' => 'StringLength','options' => ['min' => 3]]]]);
$filter->setData($this->params()->fromPost());
if (! $filter->isValid()) {
// 回傳錯誤訊息
}

八、錯誤處理與日誌

  • 例外處理:在控制器或中介層捕捉,或使用 Mezzio Problem Details(API 適用)。
  • 安裝日誌:composer require laminas/laminas-log,於工廠注入 Logger。

九、Mezzio(中介層)快速起步

Mezzio 建構在 PSR-7/PSR-15 上,適合 API 與高自由度專案。

  • 新增路由(config/routes.php):
$app->get('/health', App\Handler\HealthCheckHandler::class, 'health');

處理器(src/App/Handler/HealthCheckHandler.php):

<?php namespace App\Handler;

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as Handler;
use Psr\Http\Message\ResponseInterface as Response;
use Laminas\Diactoros\Response\JsonResponse;

class HealthCheckHandler implements Handler
{
public function handle(Request $request): Response
{
return new JsonResponse(['status' => 'ok', 'time' => time()]);
}
}

中介層(Middleware)可用來做驗證、CORS、日誌、錯誤攔截,依序在 config/pipeline.php 排入。

十、部署最佳實務

  • Config:把敏感設定放 local.php 或環境變數($_ENV)。
  • 快取:啟用 OPcache;開啟路由與設定快取(MVC:config_cache_enabled)。
  • Web 伺服器:Nginx/Apache 指向 public/;禁止存取 module/config/
  • 佈署:使用 Composer 安裝 --no-dev、跑資料遷移(Doctrine/自製 SQL)、加上健康檢查路由。

十一、常見踩雷

  • 忘記註冊模組:新增模組後一定要在 modules.config.php 加上。
  • 工廠未綁定:控制器/服務沒在 factories 註冊會報「無法建立服務」。
  • 視圖路徑錯誤:檔名與資料夾需對應 view/{module}/{controller}/{action}.phtml
  • DB 連線字元集:MySQL 請使用 utf8mb4,避免表情符號亂碼。

十二、學習地圖與資源

結語

如果你偏好穩健的 MVC 與明確的模組化,選 Laminas MVC;若你要高彈性的 API 與中介層管線,選 Mezzio。兩者都延續了 Zend 的工程嚴謹與企業級品質。照本文步驟走,你已具備做出能上線的專案雛形,接著把業務邏輯放進 Service、用表單與驗證把資料關卡守好,Laminas 就能長期托住你的產品。