Skip to content

Commit 9fd41b0

Browse files
Add canonical links for documentation pages (#329)
1 parent 6c2d019 commit 9fd41b0

3 files changed

Lines changed: 157 additions & 0 deletions

File tree

app/Http/Controllers/ShowDocumentationController.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace App\Http\Controllers;
44

5+
use App\Services\DocsVersionService;
56
use App\Support\CommonMark\CommonMark;
7+
use Artesaos\SEOTools\Facades\SEOMeta;
68
use Artesaos\SEOTools\Facades\SEOTools;
79
use Illuminate\Contracts\View\Factory as ViewFactory;
810
use Illuminate\Http\RedirectResponse;
@@ -49,6 +51,13 @@ public function __invoke(Request $request, string $platform, string $version, ?s
4951
$title = $pageProperties['title'].' - NativePHP '.$platform.' v'.$version;
5052
$description = Arr::exists($pageProperties, 'description') ? $pageProperties['description'] : 'NativePHP documentation for '.$platform.' v'.$version;
5153

54+
$canonicalUrl = app(DocsVersionService::class)->determineCanonicalUrl(
55+
platform: $platform,
56+
page: $page,
57+
);
58+
59+
SEOMeta::setCanonical($canonicalUrl);
60+
5261
SEOTools::setTitle($title);
5362
SEOTools::setDescription($description);
5463

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace App\Services;
4+
5+
class DocsVersionService
6+
{
7+
public function determineCanonicalUrl(string $platform, string $page): string
8+
{
9+
$latestVersion = $platform === 'mobile' ? config('docs.latest_versions.mobile') : config('docs.latest_versions.desktop');
10+
11+
$page = $this->resolveOldVersionApisWithPluginsCorePage($platform, $latestVersion, $page);
12+
13+
$latestPagePath = resource_path("views/docs/{$platform}/{$latestVersion}/{$page}.md");
14+
15+
$page = file_exists($latestPagePath) ? $page : 'getting-started/introduction';
16+
17+
return route('docs.show', [
18+
'platform' => $platform,
19+
'version' => $latestVersion,
20+
'page' => $page,
21+
]);
22+
}
23+
24+
/**
25+
* Handle renamed paths (e.g., apis/* moved to plugins/core/*)
26+
*/
27+
public function resolveOldVersionApisWithPluginsCorePage(string $platform, string $latestVersion, string $page): string
28+
{
29+
if (str_starts_with($page, 'apis/')) {
30+
$remappedPage = 'plugins/core/'.substr($page, 5);
31+
$remappedPath = resource_path("views/docs/{$platform}/{$latestVersion}/{$remappedPage}.md");
32+
33+
if (file_exists($remappedPath)) {
34+
$page = $remappedPage;
35+
}
36+
}
37+
38+
return $page;
39+
}
40+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
namespace Tests\Unit;
4+
5+
use App\Services\DocsVersionService;
6+
use Illuminate\Foundation\Testing\RefreshDatabase;
7+
use PHPUnit\Framework\Attributes\DataProvider;
8+
use PHPUnit\Framework\Attributes\Test;
9+
use Tests\TestCase;
10+
11+
class DocsVersionServiceTest extends TestCase
12+
{
13+
use RefreshDatabase;
14+
15+
protected DocsVersionService $docsVersionService;
16+
17+
protected function setUp(): void
18+
{
19+
parent::setUp();
20+
21+
$this->docsVersionService = app(DocsVersionService::class);
22+
}
23+
24+
public static function platformDataProvider(): array
25+
{
26+
return [
27+
'platform=mobile' => ['mobile'],
28+
'platform=desktop' => ['desktop'],
29+
];
30+
}
31+
32+
#[Test]
33+
#[DataProvider('platformDataProvider')]
34+
public function it_points_to_the_latest_version_when_page_exists_in_latest(
35+
string $platform,
36+
): void {
37+
$latestVersion = $platform === 'mobile' ? config('docs.latest_versions.mobile') : config('docs.latest_versions.desktop');
38+
39+
$url = $this->docsVersionService->determineCanonicalUrl(
40+
platform: $platform,
41+
page: 'getting-started/introduction',
42+
);
43+
44+
$expected = route('docs.show', [
45+
'platform' => $platform,
46+
'version' => $latestVersion,
47+
'page' => 'getting-started/introduction',
48+
]);
49+
50+
$this->assertEquals($expected, $url);
51+
}
52+
53+
#[Test]
54+
public function it_remaps_mobile_apis_pages_to_plugins_core_when_the_page_exists_there_in_latest(): void
55+
{
56+
$url = $this->docsVersionService->determineCanonicalUrl(
57+
platform: 'mobile',
58+
page: 'apis/camera',
59+
);
60+
61+
$expected = route('docs.show', [
62+
'platform' => 'mobile',
63+
'version' => config('docs.latest_versions.mobile'),
64+
'page' => 'plugins/core/camera',
65+
]);
66+
67+
$this->assertEquals($expected, $url);
68+
}
69+
70+
#[Test]
71+
public function it_points_to_the_latest_version_of_getting_started_introduction_when_page_does_not_exist_in_latest(): void
72+
{
73+
// concepts/ci-cd only exists in version 1 of mobile documentation
74+
$url = $this->docsVersionService->determineCanonicalUrl(
75+
platform: 'mobile',
76+
page: 'concepts/ci-cd',
77+
);
78+
79+
$expected = route('docs.show', [
80+
'platform' => 'mobile',
81+
'version' => '3',
82+
'page' => 'getting-started/introduction',
83+
]);
84+
85+
$this->assertEquals($expected, $url);
86+
}
87+
88+
#[Test]
89+
#[DataProvider('platformDataProvider')]
90+
public function it_points_to_the_latest_version_of_getting_started_introduction_when_page_does_not_exist(
91+
string $platform,
92+
): void {
93+
$latestVersion = $platform === 'mobile' ? config('docs.latest_versions.mobile') : config('docs.latest_versions.desktop');
94+
95+
$url = $this->docsVersionService->determineCanonicalUrl(
96+
platform: $platform,
97+
page: 'non-existent-page',
98+
);
99+
100+
$expected = route('docs.show', [
101+
'platform' => $platform,
102+
'version' => $latestVersion,
103+
'page' => 'getting-started/introduction',
104+
]);
105+
106+
$this->assertEquals($expected, $url);
107+
}
108+
}

0 commit comments

Comments
 (0)