Laravelではパンくずを表示する方法として、laravel-breadcrumbsというライブラリがある。これを使うと、指定のフォーマットに沿って記述をすれば、リンク付きのパンくずを簡単に生成できる。親となるページのタイトルやリンクの変更も簡単。
ここでは、このlaravel-breadcrumbsを使って、パンくずの構造化マークアップを実装する方法を紹介します。
※前提: laravel-breadcrumbsでパンくずが表示の設定が完了していること。
laravel-breadcrumbsの導入とパンくずの表示方法は、公式ページをご参考ください。
Laravel-breadcrumbsの概要
Laravel-breadcrumbsを使って、ページにパンくずを表示する時は、以下のようにBreadcrumbsクラスのrenderメソッドを使います。
ページのURIにスラッグが必要な場合は、第2引数で変数として渡します。
{{ Breadcrumbs::render('登録名') }}
{{ Breadcrumbs::render('登録名', 変数) }}
パンくず名の登録は、routes/breadcrumbs.phpで行います。URLは通常ルート名で指定します。(例: route(‘home’) )
Breadcrumbs::for('パンくず名', function ($trail) {
$trail->push('タイトル', 'URL');
});
パンくずの階層が深くなる場合は以下のようにします。
Breadcrumbs::for('パンくず名', function ($trail) {
$trail->parent('親のパンくず名');
$trail->push('タイトル', 'URL');
});
ポイントはparentメソッドで、親のパンくず名を入力すれば、その設定を抽出してくれることです。
変数を渡す場合は以下のようになります。(変数を配列で渡した場合)
Breadcrumbs::for('パンくず名', function ($trail, $変数) {
$trail->parent('親のパンくず名', $変数->プロパティ名);
$trail->push($変数名->タイトルのプロパティ名, route('ルート名', $変数名->スラッグのプロパティ名));
});
そして、冒頭で紹介した、renderメソッドを使うと、liタグなどを付けていい感じにHTMLとして出力してくれます。
構造化マークアップ用のタイトルとURLの取得
パンくずの構造化マークアップをする場合は、html形式だと余計なデータが多いので、「generate」という便利なメソッドを使って、編集しやすいデータを取得します。
Breadcrumbs::generate('登録名')
Breadcrumbs::gererate('登録名', 変数)
generateメソッドを使うと、パンくずに使うタイトルとURLをコレクション形式で出力してくれます。
コレクションはLaravel独自の便利機能で、PHPの配列をより便利に編集できる形式です。mapなどのメソッドが使えるようになります。
階層ごとに、{ タイトル, URL } としてひとまとめにしてくれているので、データ取得がとても便利です。
構造化マークアップデータ作成の大まかな流れ
ここでは、構造化マークアップの作成をJsonLdという専用のファサードを作成して行います。作成の大まかな流は以下になります。
- パンくず生成のためのクラス(JsonLd)を作成
- サービスプロバイダに登録
- ファサードとして登録
- エイリアスを登録
- ヘルパーを作成
- ブレードに追記
処理をブレードに記述しても同じことはできますが、基本、ブレードにロジックは極力記述したくないので、サービスとして登録して、どこでも呼び出せるようにしたり、ヘルパーとして別ファイルに処理を記述するようにします。
パンくず生成のためのクラス(JsonLd)を作成
まずは、パンくずを生成するためのベースとなるJsonLdクラスを作成します。
クラスを作成する場所はどこでもいいのですが、今回は、app > Services > JsonLd.php に作成します。
<?php
namespace App\Services;
class JsonLd
{
//構造化データを入れる変数を定義
private $collection;
//空のコレクションを格納
public function __construct() {
$this->collection = collect([]);
}
//Collectionへの追加
public function pushCollection($item) {
if (is_array($item)) {
$item = collect($item);
}
$this->collection->push($item);
return $this;
}
//パンくずの構造化データ形成
public function putBreadcrumbList($breadcrumbs) {
return self::pushCollection([
'@type' => 'BreadcrumbList',
'itemListElement' => $breadcrumbs->map(function($breadcrumb, $i) {
return ['@type'=>'ListItem','position'=>$i+1, 'name'=> $breadcrumb->title, 'item'=>$breadcrumb->url ];
}),
]);
}
//コレクションをJsonに変換
public function renderJson() {
return json_encode($this->collection->map(function($item){
$item['@context'] = 'http://schema.org';
return $item;
})->values());
}
}
作成時の重要なポイントは以下です。
- 名前空間とファイルのパスを一致させる。
- 構造化データの編集はcollectionで行う。
- 最後に、collectionからJsonに変換する。
特に、名前空間とファイルのパスを一致させないとエラーになるので注意が必要です。詳細は以下をご参考ください。
(参考)【Laravel】クラスが見つからない時の対処法:Class not foundとdoes not comply with psr-4 autoloading standard. Skipping.
パンくずの構造化マークアップは、以下のように、@contentと@typeの下に、「itemListElement」を付けて、その中に階層の分だけデータを記述していきます。
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [{
"@type": "ListItem",
"position": 1,
"name": "Books",
"item": "https://example.com/books"
},{
"@type": "ListItem",
"position": 2,
"name": "Science Fiction",
"item": "https://example.com/books/sciencefiction"
},{
"@type": "ListItem",
"position": 3,
"name": "Award Winners"
}]
}
</script>
これを生成するのが、以下の処理です。(※この時点では処理結果はコレクション。JSONではない)
//パンくずの構造化データ形成
public function putBreadcrumbList($breadcrumbs) {
return self::pushCollection([
'@type' => 'BreadcrumbList',
'itemListElement' => $breadcrumbs->map(function($breadcrumb, $i) {
return ['@type'=>'ListItem','position'=>$i+1, 'name'=> $breadcrumb->title, 'item'=>$breadcrumb->url ];
}),
]);
}
引数として渡す、$breadcrumbsには階層の分だけ、[ title: ‘タイトル名’, url: ‘URL’ ]を組み合わせたデータが入っています。
これを、プライベートのクラスプロパティ$collectionに追加します。
//Collectionへの追加
public function pushCollection($item) {
if (is_array($item)) {
$item = collect($item);
}
$this->collection->push($item);
return $this;
}
変数を作って格納しているのは、パンくず以外にも構造化マークアップを追加できるようにしているためです。(ここでは他の構造化マークアップについての記述は割愛)
最後に、構造化データの詰まったコレクションをJsonに変換する処理を用意します。
//コレクションをJsonに変換
public function renderJson() {
return json_encode($this->collection->map(function($item){
$item['@context'] = 'http://schema.org';
return $item;
})->values());
}
json_encodeはPHPの関数で、連想配列やコレクションなどをJSON形式に変換します。
サービスプロバイダに登録
サービスプロバイダーとは、一まとまりの処理を名前を付けて保管できるLaravelの機能です。これを使うと、処理の呼び出しを、自分で設定した名前で簡単に呼び出せます。
よく、サービスやサービスコンテナという言葉と一緒に使われます。この登録した処理がサービスで、そのサービスを保管しているのがサービスコンテナ。サービスを登録するのが、サービスプロバイダーになります。
関連する用語をまとめると以下になります。
用語 | 内容 | 備考 |
---|---|---|
サービス | 登録した処理 | app()->make(‘登録したサービス名’) で呼び出す。登録するのはクラス。 |
サービスコンテナ | サービスが保存されている場所 | Dockerのコンテナとは別物 |
サービスプロバイダ | サービスの登録 | AppServiceProvider.phpなどデフォルトで登録できるファイルがある。自分でも作成可能。 |
ファサード | クラスメソッドをインスタンスメソッドのように使える | インスタンス化の処理をしてくれる。(記述が一部省略できる)。登録時にサービス名を使う。 |
エイリアス | 省略した名前 | クラスをグローバル空間名で呼び出せる。 例 JsonLd:: (または \JsonLd::) |
ヘルパ | 自作のメソッドを登録 | 登録したメソッドはどこからでも呼び出せる。 |
サービスプロバイダを使ってサービスを登録していきます。自分でサービスプロバイダーを作成することもできますが、今回は、既存の app/Providers/AppServiceProvider.php を使います。
AppServiceProvider.phpを開き、register関数に処理を追記します。
public function register()
{
$this->app->singleton('サービス名', function ($app) {
処理;
});
}
ここで指定したサービス名を今後の呼び出しでも使います。
singletonメソッドは、登録したサービスの生成を1つのインスタンスしか行わない方法です。どこで、このサービスを呼び出しても同じインスタンスを参照します。
singletonの他に、bindメソッドもあります。bindメソッドで登録したサービスは、呼び出す毎に新しいインスタンスを生成します。
AppServiceProvider.phpは以下になります。
singletonの登録処理の中でJsonLdインスタンスを生成しているので、上部でuse宣言することを忘れないでください。
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Services\JsonLd;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->singleton('JsonLd', function () {
return new JsonLd();
});
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//
}
}
以上でサービスの登録が完了です。
この段階で、JsonLdを呼び出す場合は、appヘルパのmakeメソッドを使います。生成したインスタンスに対して「->メソッド名()」とすればメソッドを呼び出せます。
$obj = app()->make(‘JsonLd’);
$obj->putBreadcrumbList($breadcrumbs);
この記述では、app()->make(‘メソッド名’) という処理が必要になりますが、次のファサードとして登録することで、 App\Services\JsonLd::メソッド名() のように、よりわかりやすい記述で呼び出せるようになります。
ファサードの登録
ファサードとは、クラスをインスタンス化しなくても、静的(static)メソッドのように使える機能のことです。
名前空間\クラス名::メソッド() で呼び出せるようになります。
ポイントは「インスタンス化不要」という点。いちいちインスタンス化する手間を省けるため、コードの可読性が向上します。
聴き慣れない用語なので難しそうに感じますが、実態は単純な機能です。実際、そこまで大幅に処理を改善してくれる機能でもありません。あれば便利ぐらいのものです。
ちなみに、Facadeとは入り口といった意味。家の中のメソッドにアクセスして呼び出さなくても、入り口にアクセスすれば、そのメソッドを呼び出せるというニュアンスです。
Facadeの登録はとても簡単です。ポイントは以下の3点です。
作成するファイルの場所もファイル名も指定はありません。今回はわかりやすく、
app/Facades/JsonLdFacade.php
とします。(ファイル名は、JsonLdにすると最初に定義したクラスのファイルと被るので、Facadeを付けておくと管理しやすいです。)
名前空間と定義するクラス名は、ファイルのパスに合わせる必要があります。これをしないと、composerのdump-autoloadでエラーが発生しクラスを生成できません。
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class JsonLdFacade extends Facade
{
protected static function getFacadeAccessor()
{
return 'JsonLd';
}
}
これでファサードの登録は完了です。とても簡単ですね。
ここまでの処理で、 App\Services\JsonLd::putBreadcrumbList($breadcrumbs) でメソッドを呼び出せるようになりました。
ただ、まだ長いのでよりシンプルに、JsonLd::メソッド() で呼び出せるようにします。
エイリアスの設定
グローバルな名前空間(クラス名のみ)で呼び出すためにはエイリアスを設定します。この処理もとても簡単です。
config/app.php ファイルの下の方に「’aliase’=>」と書かれた場所があるので、ここに1行追加します。
‘エイリアス名’ => 名前空間::class
'aliases' => [
//省略
'JsonLd' => App\Facades\JsonLdFacade::class,
],
以上で、JsonLd::メソッド() でどこからでも呼び出せるようになりました。記述はとてもシンプルです。
グローバルな名前空間であることを明示する場合は、クラス名の冒頭に「バックスラッシュ \」を付けて記述することもあります。どちらも同じ処理です。
\JsonLd::メソッド()
ここまでの変遷をまとめると以下になります。
項目 | メソッドの呼び出し |
---|---|
サービス登録 | app()->make(‘JsonLd’)->メソッド() |
ファサード登録 | App\Services\JsonLd::メソッド |
エイリアス登録 | JsonLd::メソッド |
ヘルパを作成
ここまで作成できたら、登録したファサードのエイリアス(長いので以下ファサード)を使って、ビューの中に処理を記述することができます。
ただし、ビューにPHPの処理を記述することは極力避けたいので、ヘルパー関数を作って別に切り出すようにします。
ヘルパとは、自分で登録できるメソッドのことです。登録しておけば、どこからでも呼び出すことができます。サイト構築を助けてくれるのでヘルパーです。
ヘルパの作成手順
ヘルパ作成の大まかな流れは以下になります。
- ヘルパ用のファイルを作成(場所や名前は任意)
- 関数を定義する
- composer.jsonのautloadにファイルパスを追記
- dump-autoloadで読み込みを更新
ヘルパ用のファイルを作成
まずは、ヘルパを記述するファイルを作成します。場所や名前はどこでもいいです。今回は、
app > helper.php とします。
関数を定義する
helper.phpの中に関数を定義します。ヘルパの記述は、最初にif文で、これから作成しようとしている関数が既に登録されていなか確認します。
デフォルトで用意されているメソッドも上書きできてしまうので、その回避処理です。
<?php
if (! function_exists('ヘルパ名')) {
function ヘルパ名(引数)
{
//処理;
}
}
今回は、構造化マークアップの処理を記述する。パンくずのみなのでとてもシンプルだが、実際に使うときは、OrganizatinoやArticleなど他の構造化の処理もここに記述する。(例えば、ページによって切り分ける場合は、if文でルート名を指定するなど。)
if(! function_exists( "structured_markup" )){
function structured_markup($breadcrumbs){
//パンくず構造化マークアップ
JsonLd::putBreadcrumbList($breadcrumbs);
}
}
composer.jsonのautloadにファイルパスを追記
作成したファイルがヘルパとして読み込まれるように、composer.jsonのautloadにファイルパスを追記します。
"autoload": {
"files": [
"app/helpers.php"
],
}
dump-autoloadで読み込みを更新
composerのdump-autoloadコマンドを実行して、ファイルをリロードします。
$ composer dump-autoload
以上で、ヘルパーの作成は完了です。
ブレードに追記
最後にブレードで処理を呼び出します。
パンくずの構造化マークアップはすべてのページに設置するものなので、共通テンプレートに処理を記述します。
(パンくずでslugを必要とするページがある場合は、if文で条件分岐させる。)
ブレードの例は以下のようになります。
@php
//パンくずの生成
$currentRoute = route()->getName();
$breadcrumbsHtml = Breadcrumbs::render( $currentRoute );
$breadcrumbs = Breadcrumbs::generate( $currentRoute );
//Breadcrumbsの構造化データ生成
structured_markup($breadcrumbs);
@endphp
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta name="description" content="説明">
<meta name="keywords" content="キーワード">
<title>タイトル</title>
{{-- Structured Markup --}}
<script type="application/ld+json">
{!! JsonLd::renderJson() !!}
</script>
</head>
<body">
@include('layouts.header')
{{ $breadcrumbsHtml }}
<div class="@yield('contentClass')">
@yield('content')
</div>
@include('layouts.footer')
</body>
</html>
{!! JsonLd::renderJson() !!} は、生成した構造化マークアップのコレクションをJSONに変換し出力しています。
{{ $breadcrumbsHtml }} は、パンくずのHTMLを出力しています。
以上で記述は完了です。
構造化マークアップのチェック
実装が完了したら、構造化マークアップが正しく組めているか、Googleのリッチリザルトチェックツールで確認します。
ブラウザをリロードして、ソースコードを開き、script type=”application/ld+json”となっているところをコピーします。
リッチリザルトチェックツールのコード検証に貼り付けて実行する。「リッチリザルトの対象です」と表示され、「パンくずリスト」でデータが意図した通りに認識していればOK。
以上で、Laravel-breadcrumbsを使った、パンくずの構造化マークアップを実装が完了です。
おまけ|Breadcrumbs::renderを整形する方法
今回は、Breadcrumbs::generateを使って元になるデータを生成しましたが、Breadcrumbs::renderをあれこれすることで同じデータを取得することもできます。
preg_match_all( "|href=\"(.*)\"|", $breadcrumbs, $urls );
preg_match_all( "|<a [^>]+>(.*)</a>|", $breadcrumbs, $titles );
preg_match_all( "|<li[^>]+active\">(.*)</li>|", $breadcrumbs, $currentTitle );
$items = [];
$num = count($urls[1]);
for ($i=0; $i<$num; $i++){
array_push($items, []);
}
foreach ($urls[1] as $i => $url){
array_push($items[$i], $url);
}
foreach ($titles[1] as $i => $title){
array_push($items[$i], $title);
}
if( Route::currentRouteName() === 'top' ){
$currentTitle = "HOME";
}
else{ $currentTitle = $currentTitle[1][0];}
$currentUrl= url()->current();
array_push($items, [$currentUrl, $currentTitle]);
JsonLd::putBreadcrumbList($items);
preg_match_allはPHPのメソッドで、文字列データの中から、指定したパターンにマッチするデータを抜き出し、指定した変数に格納します。
preg_match_allで取得したデータは以下のようになります。
array:2 [▼
0 => array:3 [▼
0 => "<a href="http://localhost:13001">Home</a>"
1 => "<a href="http://localhost:13001/blog">Blog</a>"
2 => "<a href="http://localhost:13001/blog/technology">Technology</a>"
]
1 => array:3 [▼
0 => "Home"
1 => "Blog"
2 => "Technology"
]
]
取得したデータは、1つ目の配列に、パターンにマッチしたもの。2つ目の配列には、タグを除去したものが入ります。
これを利用して、[ url1, url2, url3 ]という配列と、[ title1, title2, title3] をforeachとarray_pushを使って、[ [url1, title1], [url2, title2], [url3, title3] ]に整形しています。
こうすれば、Breadcrumbs::generateで生成したデータをコレクションから配列に変換したものと同じデータを取得することができます。
パンくずを生成する「JsonLd::putBreadcrumbList($items);」の処理は以下になります。
//パンくずの構造化マークアップデータの作成(コレクション形式)
public function putBreadcrumbList($items) {
return self::pushCollection([
'@type' => 'BreadcrumbList',
'itemListElement' => collect($items)->map(function($item, $i) {
return ['@type'=>'ListItem','position'=>$i+1, 'name'=> $item[1], 'item'=>$item[0] ];
}),
]);
}
//コレクションをJsonに変換
public function renderJson() {
return json_encode($this->collection->map(function($item){
$item['@context'] = 'http://schema.org';
return $item;
})->values());
}
冗長なので、Breadcrumbs::generateを使いましょう。便利なメソッドを用意し、フリーで配布してくれている方々に感謝です。