Laravelでsitemap.xmlを作成する方法

Laravelでsitemap.xmlを生成する方法をご紹介します。
本記事で作成するsitemapは、以下の4種類を作成します。比較的規模の大きいサイトの場合、サイトマップを分けることが多いと思います。
また、サイトマップの生成には「roumen/sitemap」というライブラリをLaravelに追加して実装します。

ファイル名詳細
sitemap.xml大元のサイトマップです。このサイトマップに分割したサイトマップのURLを記載します。
sitemap-misc.xmlサイトのトップページとHTMLのサイトマップを記載します。
sitemap-tax-category.xmlカテゴリーページのURLを記載します。
sitemap-pt-post-{年}-{月}.xml月毎の記事のURLを記載します。

テーブルとURL設計

実装を進めるため、記事関連のデータモデルとURLを下記のように設計します。

テーブル

記事テーブルとカテゴリーのテーブルを作成します。カテゴリの階層などは、ここでは考慮しません。
また、サイトマップを生成するためのデータのみに記載しています。

記事(posts)

カラム説明
id記事のID
category_id記事が属するカテゴリーのID
published_at記事の公開日時

カテゴリ(categories)

カラム説明
idカテゴリのID

URL

URLは以下のように定義します。

Path説明
/トップページ
/posts/{postId}記事詳細ページ
/categories/{categoryId}カテゴリーページ
/sitemapユーザーに見せるサイトマップのページ

サイトマップ生成の実装

ライブラリの追加とルーティング設定

サイトマップの作成には、roumen/sitemap というライブラリを利用しますので、下記のコマンドを実行して追加します。
※ Laravel5.4以下の場合は、ServiceProviderの設定も必要なため本家のドキュメント参照ください。

$ composer require roumen/sitemap

$ php artisan vendor:publish --provider="Roumen\Sitemap\SitemapServiceProvider"

ルーティングは下記のように設定します。

# routes/web.php

Route::get('sitemap', 'SitemapController@html');
Route::get('sitemap.xml', 'SitemapController@xml');
Route::get('sitemap-misc.xml', 'SitemapController@misc');
Route::get('sitemap-tax-category.xml', 'SitemapController@category');
Route::get('sitemap-pt-post-{date}.xml', 'SitemapController@monthlyPost');

sitemapの作成

これで準備が整いましたので、sitemapの作成部分に入ります。
日付の処理が必要になりますので、Carbonを使用しています。

コードは下記のようになります。

# app/Http/Controllers/SitemapController.php

<?php

namespace App\Http\Controllers;

use Carbon\Carbon;
use Roumen\Sitemap\SitemapServiceProvider;

use App;
use App\Post;
use App\Category;

class SitemapController extends Controller
{

    /**
     * sitemap.xml
     */
    public function xml()
    {
        $sitemap = App::make("sitemap");

        $sitemap->addSitemap(url('sitemap-misc.xml'));
        $sitemap->addSitemap(url('sitemap-tax-category.xml'));

        Post::where('published_at', '<=', Carbon::now())
            ->get()
            ->groupBy(function($post) {
                return $post->published_at->format('Y-m');
            })
            ->each(function($posts, $key) use ($sitemap) {
                $sitemap->addSitemap(url("sitemap-pt-post-{$key}.xml"));
            });

        return $sitemap->render('sitemapindex');
    }

    /**
     * sitemap-misc.xml
     */
    public function misc()
    {
        $now = Carbon::now();

        $sitemap = App::make("sitemap");

        $sitemap->add(url('/'), $now, '1', 'monthly');
        $sitemap->add(url('/sitemap'), $now, '0.5', 'monthly');

        $sitemap->store('xml', 'sitemap-misc');
    }

    /**
     * カテゴリー(sitemap-tax-category.xml)
     */
    public function category()
    {
        $now = Carbon::now();

        $sitemap = App::make("sitemap");

        Category::all()
            ->each(function($category) use ($sitemap, $now) {
                $sitemap->add(url("/categories/{$category->id}"), $now, '0.5', 'weekly');
            });

        $sitemap->store('xml', 'sitemap-tax-category');
    }

    /**
     * 月毎の記事ページ sitemap-pt-post-{年}-{月}.xml
     */
    public function monthlyPost($dateStr)
    {
        try {
            $date = new Carbon($dateStr);

            $posts = Post::published()
                ->toPublishedAt(Carbon::now())
                ->whereYear('published_at', '=', $date->year)
                ->whereMonth('published_at', '=', $date->month)
                ->get();

            if (!$posts->count()) {
                throw new \Exception();
            }
        } catch (\Exception $e) {
            // 無効な日付, 登録の無い月は404扱いとする
            return response([], 404);
        }

        $sitemap = App::make("sitemap");

        $posts->each(function($post) use ($sitemap) {
            $sitemap->add(url("/posts/{$post->id}"), $post->updated_at, '0.8', 'weekly');
        });

        $sitemap->store('xml', "sitemap-pt-post-{$dateStr}");
    }
}

わかりにくい点はこのあたりかと思います。

sitemap.xml
月毎の記事のサイトマップURLの生成には、全記事を公開日時でグループ化してURLをリストアップする。

sitemap-pt-post-{年}-{月}.xml
ルーティングのパラメータとして、日付を受け取り、それをもとに記事を取得する。
記事が存在しないパラメーターの場合は全て404にする。

おわりに

本記事では、Laravelでroumen/sitemapを使い、sitemap.xmlを生成する方法を紹介しました。

実際の実装部分ではLaravelのコレクションを利用すると簡単に記述することができます。

コレクションでよく利用するメソッドについては、「【サンプル付き】LaravelのCollectionの頻出メソッド15選」でも紹介していますので、よければ閲覧ください。

この記事と関連している記事