首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

Agavi MVC 框架實戰 (四):多格式輸出搜尋引擎 - HTML/JSON/XML/RSS 同一 Action 切換

     編輯:PHP 綜合知識

核心優勢:一個 Action,多種 View

Agavi 透過 **View 類別命名慣例** 實現內容協商:
`SearchSuccessView` (HTML) → `SearchJsonView` (JSON) → `SearchXmlView` (XML) → `SearchRssView` (RSS)。
客戶端透過 `Accept` 標頭或 URL 後綴 (`.json`, `.xml`, `.rss`) 自動切換。

建立 SearchAction

./bin/agavi action-wizard
# Module: Listing
# Action: Search
# Views: Error Success Json Xml Rss

產生:`SearchAction`、`SearchSuccessView`、`SearchErrorView`、`SearchJsonView`、`SearchXmlView`、`SearchRssView`。

路由配置 (app/modules/Listing/config/routing.xml)

<route name="listing" pattern="^/listing" module="Listing">
  <route name=".search" pattern="^/search(\.(?Phtml|json|xml|rss))?$" action="Search" />
</route>
  • 支援 `/listing/search` (預設 HTML)、`/listing/search.json`、`.xml`、`.rss`。
  • 命名捕獲組 `format` 可在 Action 讀取 `$rd->getParameter('format')`。

Action 實作:統一資料準備

<?php
// app/modules/Listing/actions/SearchAction.class.php
class Listing_SearchAction extends AgaviAction
{
    public function execute(AgaviRequestDataHolder $rd)
    {
        // 1. 取得搜尋參數
        $criteria = [
            'manufacturer' => $rd->getParameter('manufacturer'),
            'model'        => $rd->getParameter('model'),
            'year_from'    => $rd->getParameter('year_from'),
            'year_to'      => $rd->getParameter('year_to'),
            'price_min'    => $rd->getParameter('price_min'),
            'price_max'    => $rd->getParameter('price_max'),
            'color'        => $rd->getParameter('color'),
            'certified'    => $rd->getParameter('certified') ? true : null,
        ];
        // 移除空值
        $criteria = array_filter($criteria, fn($v) => $v !== '' && $v !== null);

        // 2. 查詢資料 (Doctrine)
        $em = DoctrineHelper::getEntityManager();
        $qb = $em->createQueryBuilder()->select('l')->from('Listing', 'l')
            ->where('l.displayStatus = 1');

        foreach ($criteria as $field => $value) {
            $param = 'p_' . $field;
            switch ($field) {
                case 'year_from': $qb->andWhere("l.vehicleYear >= :$param"); break;
                case 'year_to':   $qb->andWhere("l.vehicleYear <= :$param"); break;
                case 'price_min': $qb->andWhere("l.vehicleSalePriceMin >= :$param"); break;
                case 'price_max': $qb->andWhere("l.vehicleSalePriceMax <= :$param"); break;
                default:          $qb->andWhere("l.$field = :$param");
            }
            $qb->setParameter($param, $value);
        }

        $qb->orderBy('l.recordDate', 'DESC');
        $results = $qb->getQuery()->getResult();

        // 3. 統一設定屬性,所有 View 共用
        $this->setAttribute('results', $results);
        $this->setAttribute('criteria', $criteria);
        $this->setAttribute('count', count($results));

        return 'Success'; // 對應 Success/Json/Xml/Rss View
    }

    public function validate(AgaviValidator $validator)
    {
        $validator->addRule('year_from', new AgaviIntegerValidator(['min' => 1900, 'max' => date('Y')+1]));
        $validator->addRule('year_to',   new AgaviIntegerValidator(['min' => 1900, 'max' => date('Y')+1]));
        $validator->addRule('price_min', new AgaviNumericValidator(['min' => 0]));
        $validator->addRule('price_max', new AgaviNumericValidator(['min' => 0]));
    }
}

各格式 View 實作

HTML View (SearchSuccessView)

<?php
// app/modules/Listing/views/SearchSuccessView.class.php
class Listing_SearchSuccessView extends AgaviHtmlView
{
    public function setupHtml()
    {
        $this->setLayout('Master');
    }
}
<?php // app/modules/Listing/templates/SearchSuccess.php ?>
<h1>車輛搜尋結果 (<?= $t->getAttribute('count') ?> 筆)</h1>

<form method="GET" action="<?= $ro->gen('listing.search') ?>" class="search-form row g-3">
  <div class="col-md-3"><input type="text" name="model" class="form-control" placeholder="車型" value="<?= $t->getAttribute('criteria.model', '') ?>"></div>
  <div class="col-md-2"><input type="number" name="year_from" class="form-control" placeholder="年份從" value="<?= $t->getAttribute('criteria.year_from', '') ?>"></div>
  <div class="col-md-2"><input type="number" name="year_to" class="form-control" placeholder="年份至" value="<?= $t->getAttribute('criteria.year_to', '') ?>"></div>
  <div class="col-md-2"><input type="number" name="price_min" class="form-control" placeholder="最低價" value="<?= $t->getAttribute('criteria.price_min', '') ?>"></div>
  <div class="col-md-2"><input type="number" name="price_max" class="form-control" placeholder="最高價" value="<?= $t->getAttribute('criteria.price_max', '') ?>"></div>
  <div class="col-md-1"><button type="submit" class="btn btn-primary w-100">搜尋</button></div>
</form>

<div class="table-responsive mt-3">
  <table class="table table-striped">
    <thead><tr><th>車型</th><th>年份</th><th>顏色</th><th>里程</th><th>價格區間</th><th>認證</th><th>操作</th></tr></thead>
    <tbody>
      <?php foreach ($t->getAttribute('results') as $listing): ?>
        <tr>
          <td><?= $listing->getVehicleModel() ?></td>
          <td><?= $listing->getVehicleYear() ?></td>
          <td><?= $listing->getVehicleColor() ?></td>
          <td><?= number_format($listing->getVehicleMileage()) ?> km</td>
          <td><?= number_format($listing->getVehicleSalePriceMin()) ?> - <?= number_format($listing->getVehicleSalePriceMax()) ?></td>
          <td><?= $listing->getVehicleIsCertified() ? '是' : '否' ?></td>
          <td><a href="<?= $ro->gen('listing.display', ['id' => $listing->getRecordId()]) ?>" class="btn btn-sm btn-outline-primary">詳情</a></td>
        </tr>
      <?php endforeach; ?>
    </tbody>
  </table>
</div>

JSON View (SearchJsonView)

<?php
// app/modules/Listing/views/SearchJsonView.class.php
class Listing_SearchJsonView extends AgaviJsonView
{
    // AgaviJsonView 自動將 $this->getAttributes() 以 JSON 輸出
    // 可覆寫 getDefaultOutputOptions() 自定義 json_encode 選項
    public function getDefaultOutputOptions(): array
    {
        return JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT;
    }
}

輸出範例:

{
  "results": [
    {
      "recordId": 1,
      "vehicleModel": "Boxster",
      "vehicleYear": 2005,
      "vehicleColor": "Yellow",
      "vehicleMileage": 15457,
      "vehicleSalePriceMin": 35000,
      "vehicleSalePriceMax": 40000,
      "vehicleIsCertified": true
    }
  ],
  "criteria": { "model": "Boxster" },
  "count": 1
}

XML View (SearchXmlView)

<?php
// app/modules/Listing/views/SearchXmlView.class.php
class Listing_SearchXmlView extends AgaviXmlView
{
    public function getDefaultOutputOptions(): array
    {
        return ['format_output' => true, 'encoding' => 'UTF-8'];
    }

    // 可選:自定義 XML 結構
    public function renderXml(AgaviDomDocument $dom)
    {
        $root = $dom->createElement('searchResults');
        $root->setAttribute('count', $this->getAttribute('count'));
        $dom->appendChild($root);

        foreach ($this->getAttribute('results') as $listing) {
            $item = $dom->createElement('listing');
            $item->setAttribute('id', $listing->getRecordId());
            foreach (['vehicleModel','vehicleYear','vehicleColor','vehicleMileage','vehicleSalePriceMin','vehicleSalePriceMax','vehicleIsCertified'] as $field) {
                $el = $dom->createElement($field, $listing->{'get' . ucfirst($field)}());
                $item->appendChild($el);
            }
            $root->appendChild($item);
        }
    }
}

RSS View (SearchRssView)

<?php
// app/modules/Listing/views/SearchRssView.class.php
class Listing_SearchRssView extends AgaviRssView
{
    public function setupRss()
    {
        $this->setChannelTitle('WASP 車輛搜尋訂閱');
        $this->setChannelLink($this->getContext()->getController()->gen('listing.search', [], true));
        $this->setChannelDescription('最新符合條件的車輛列表');
        $this->setChannelLanguage('zh-TW');
    }

    public function getRssItems()
    {
        $items = [];
        foreach ($this->getAttribute('results') as $listing) {
            $items[] = [
                'title'       => $listing->getVehicleModel() . ' (' . $listing->getVehicleYear() . ')',
                'link'        => $this->getContext()->getController()->gen('listing.display', ['id' => $listing->getRecordId()], true),
                'description' => "顏色: {$listing->getVehicleColor()}, 里程: {$listing->getVehicleMileage()}km, 價格: {$listing->getVehicleSalePriceMin()} - {$listing->getVehicleSalePriceMax()}",
                'pubDate'     => $listing->getRecordDate()->format('r'),
                'guid'        => 'listing-' . $listing->getRecordId(),
            ];
        }
        return $items;
    }
}

測試多格式輸出

curl -H "Accept: application/json" https://localhost/listing/search?model=Boxster
curl https://localhost/listing/search.json?model=Boxster
curl https://localhost/listing/search.xml?model=Boxster
curl https://localhost/listing/search.rss?model=Boxster

關鍵點總結

View 類別基類Content-Type適用場景
SuccessViewAgaviHtmlViewtext/html瀏覽器網頁
JsonViewAgaviJsonViewapplication/jsonAJAX、API、行動端
XmlViewAgaviXmlViewapplication/xml企業整合、舊系統對接
RssViewAgaviRssViewapplication/rss+xml訂閱、聚合器
  • **同一 Action 邏輯**,資料準備一次,四種格式同步輸出,維護成本極低。
  • 路由中的 `format` 參數讓 URL 明確指定格式,利於快取、SEO、除錯。
  • Agavi 內建 `AgaviJsonView`/`XmlView`/`RssView` 已處理編碼、標頭、結構化輸出,開發者專注資料結構。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved