feat: enhance MusicProductionController with structured data support, improve SEO metadata handling, and add JSON-LD integration in views for better search engine visibility

This commit is contained in:
Ümit Tunç
2026-05-20 23:17:30 +03:00
parent 33c7b6a51e
commit 15896248c4
8 changed files with 367 additions and 24 deletions
@@ -0,0 +1,225 @@
<?php
namespace App\Support;
use App\Models\MusicProduction;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Str;
class MusicProductionStructuredData
{
public static function forIndex(LengthAwarePaginator $productions, string $pageUrl): array
{
$siteName = setting('site_name', config('app.name'));
$siteUrl = url('/');
$pageName = __('music_productions.meta-index-title');
$pageDescription = __('music_productions.meta-index-description');
$itemListElements = [];
$offset = ($productions->currentPage() - 1) * $productions->perPage();
foreach ($productions as $index => $production) {
$itemListElements[] = [
'@type' => 'ListItem',
'position' => $offset + $index + 1,
'item' => self::productionSchema($production),
];
}
$graph = [
self::websiteNode($siteUrl, $siteName),
self::organizationNode($siteUrl, $siteName),
[
'@type' => 'CollectionPage',
'@id' => $pageUrl . '#webpage',
'url' => $pageUrl,
'name' => $pageName,
'description' => $pageDescription,
'inLanguage' => app()->getLocale(),
'isPartOf' => ['@id' => $siteUrl . '#website'],
'breadcrumb' => ['@id' => $pageUrl . '#breadcrumb'],
'mainEntity' => ['@id' => $pageUrl . '#itemlist'],
],
[
'@type' => 'BreadcrumbList',
'@id' => $pageUrl . '#breadcrumb',
'itemListElement' => [
[
'@type' => 'ListItem',
'position' => 1,
'name' => __('music_productions.breadcrumb_home'),
'item' => $siteUrl,
],
[
'@type' => 'ListItem',
'position' => 2,
'name' => __('music_productions.nav-music-productions'),
'item' => route('music-productions.index'),
],
],
],
[
'@type' => 'ItemList',
'@id' => $pageUrl . '#itemlist',
'name' => $pageName,
'numberOfItems' => $productions->total(),
'itemListElement' => $itemListElements,
],
];
return [
'@context' => 'https://schema.org',
'@graph' => $graph,
];
}
public static function forShow(MusicProduction $production, string $pageUrl): array
{
$siteName = setting('site_name', config('app.name'));
$siteUrl = url('/');
$title = $production->translate('title');
$description = Str::limit(strip_tags((string) $production->translate('content')), 160);
$graph = [
self::websiteNode($siteUrl, $siteName),
self::organizationNode($siteUrl, $siteName),
[
'@type' => 'BreadcrumbList',
'@id' => $pageUrl . '#breadcrumb',
'itemListElement' => [
[
'@type' => 'ListItem',
'position' => 1,
'name' => __('music_productions.breadcrumb_home'),
'item' => $siteUrl,
],
[
'@type' => 'ListItem',
'position' => 2,
'name' => __('music_productions.nav-music-productions'),
'item' => route('music-productions.index'),
],
[
'@type' => 'ListItem',
'position' => 3,
'name' => $title,
'item' => $pageUrl,
],
],
],
array_merge(
[
'@type' => 'WebPage',
'@id' => $pageUrl . '#webpage',
'url' => $pageUrl,
'name' => $title,
'description' => $description,
'inLanguage' => app()->getLocale(),
'isPartOf' => ['@id' => $siteUrl . '#website'],
'breadcrumb' => ['@id' => $pageUrl . '#breadcrumb'],
'mainEntity' => ['@id' => $pageUrl . '#production'],
],
$production->updated_at ? ['dateModified' => $production->updated_at->toIso8601String()] : [],
),
array_merge(
['@id' => $pageUrl . '#production'],
self::productionSchema($production),
),
];
return [
'@context' => 'https://schema.org',
'@graph' => $graph,
];
}
/**
* @return array<string, mixed>
*/
protected static function productionSchema(MusicProduction $production): array
{
$schema = [
'@type' => filled($production->spotify_album_id) ? 'MusicAlbum' : 'CreativeWork',
'name' => $production->translate('title'),
'url' => route('music-productions.show', $production->slug),
];
if ($production->cover_image_url) {
$schema['image'] = $production->cover_image_url;
}
$content = strip_tags((string) $production->translate('content'));
if ($content !== '') {
$schema['description'] = Str::limit($content, 300);
}
if ($production->production_date) {
$schema['datePublished'] = $production->production_date->toIso8601String();
}
$clientName = $production->translate('client_name');
if ($clientName) {
$schema['creator'] = [
'@type' => 'Organization',
'name' => $clientName,
];
}
$sameAs = array_values(array_filter([
$production->spotify_url,
$production->youtube_url,
]));
if ($sameAs !== []) {
$schema['sameAs'] = $sameAs;
}
return $schema;
}
/**
* @return array<string, mixed>
*/
protected static function websiteNode(string $siteUrl, string $siteName): array
{
return [
'@type' => 'WebSite',
'@id' => $siteUrl . '#website',
'url' => $siteUrl,
'name' => $siteName,
'publisher' => ['@id' => $siteUrl . '#organization'],
'potentialAction' => [
'@type' => 'SearchAction',
'target' => [
'@type' => 'EntryPoint',
'urlTemplate' => route('music-productions.index') . '?q={search_term_string}',
],
'query-input' => 'required name=search_term_string',
],
];
}
/**
* @return array<string, mixed>
*/
protected static function organizationNode(string $siteUrl, string $siteName): array
{
$node = [
'@type' => 'Organization',
'@id' => $siteUrl . '#organization',
'name' => $siteName,
'url' => $siteUrl,
];
$logo = setting('site_logo');
if ($logo) {
$logoUrl = str_starts_with($logo, 'http') ? $logo : asset('storage/' . ltrim($logo, '/'));
$node['logo'] = [
'@type' => 'ImageObject',
'url' => $logoUrl,
];
}
return $node;
}
}