Skip to content

Commit c7fad65

Browse files
committed
unified seo data
1 parent d898f16 commit c7fad65

File tree

10 files changed

+243
-5
lines changed

10 files changed

+243
-5
lines changed

src/Contracts/HasSeo.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Elegantly\Seo\Contracts;
4+
5+
use Elegantly\Seo\SeoManager;
6+
use Elegantly\Seo\Unified\SeoUnifiedData;
7+
8+
interface HasSeo
9+
{
10+
public function getSeoData(): SeoManager|SeoUnifiedData;
11+
}

src/OpenGraph/Verticals/Website.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ class Website extends Vertical
1616

1717
public function __construct(
1818
public string $title,
19-
public Image $image,
2019
public string $url,
2120

21+
public ?Image $image = null,
2222
public ?Audio $audio = null,
2323
public ?string $description = null,
2424
public ?string $determiner = null,

src/Schemas/Schema.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Elegantly\Seo\Schemas;
4+
5+
use Elegantly\Seo\Contracts\Taggable;
6+
use Elegantly\Seo\SeoTags;
7+
use Elegantly\Seo\Tags\Script;
8+
use Illuminate\Support\Collection;
9+
10+
/**
11+
* @extends Collection<string, null|int|float|string|array|Schema>
12+
*/
13+
class Schema extends Collection implements Taggable
14+
{
15+
public function toTags(): SeoTags
16+
{
17+
return new SeoTags([
18+
new Script(
19+
type: 'application/ld+json',
20+
content: $this->toJson(),
21+
),
22+
]);
23+
}
24+
}

src/SeoManager.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44

55
use Elegantly\Seo\Contracts\Taggable;
66
use Elegantly\Seo\OpenGraph\Verticals\Vertical;
7+
use Elegantly\Seo\Schemas\Schema;
78
use Elegantly\Seo\Standard\StandardData;
89
use Elegantly\Seo\Twitter\Cards\Card;
910

1011
class SeoManager implements Taggable
1112
{
13+
/**
14+
* @param null|Schema[] $schemas
15+
*/
1216
public function __construct(
1317
public ?StandardData $standard = null,
1418
public ?Vertical $opengraph = null,
1519
public ?Card $twitter = null,
20+
public ?array $schemas = null,
21+
public ?SeoTags $customTags = null,
1622
) {}
1723

1824
public function toTags(): SeoTags
@@ -28,6 +34,14 @@ public function toTags(): SeoTags
2834
if ($this->twitter) {
2935
$tags->push(...$this->twitter->toTags());
3036
}
37+
if ($this->schemas) {
38+
foreach ($this->schemas as $schema) {
39+
$tags->push(...$schema->toTags());
40+
}
41+
}
42+
if ($this->customTags) {
43+
$tags->push(...$this->customTags);
44+
}
3145

3246
return $tags;
3347
}

src/Tags/Script.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Elegantly\Seo\Tags;
4+
5+
use Illuminate\Support\Collection;
6+
7+
class Script extends Tag
8+
{
9+
public string $tag = 'script';
10+
11+
public function __construct(
12+
public ?string $type = null,
13+
public ?string $content = null,
14+
) {
15+
$this->properties = Collection::make([
16+
'type' => $type,
17+
])->filter(fn (?string $item) => ! blank($item));
18+
}
19+
}

src/Unified/Image.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Elegantly\Seo\Unified;
4+
5+
class Image
6+
{
7+
public function __construct(
8+
public string $url,
9+
public ?string $type = null,
10+
public ?string $width = null,
11+
public ?string $height = null,
12+
public ?string $alt = null,
13+
) {
14+
//
15+
}
16+
}

src/Unified/SeoUnifiedData.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
namespace Elegantly\Seo\Unified;
4+
5+
use Elegantly\Seo\Contracts\Taggable;
6+
use Elegantly\Seo\OpenGraph\Image as OpenGraphImage;
7+
use Elegantly\Seo\OpenGraph\Locale;
8+
use Elegantly\Seo\OpenGraph\Verticals\Vertical;
9+
use Elegantly\Seo\OpenGraph\Verticals\Website;
10+
use Elegantly\Seo\Schemas\Schema;
11+
use Elegantly\Seo\SeoManager;
12+
use Elegantly\Seo\SeoTags;
13+
use Elegantly\Seo\Standard\Alternate;
14+
use Elegantly\Seo\Standard\StandardData;
15+
use Elegantly\Seo\Twitter\Cards\Card;
16+
use Elegantly\Seo\Twitter\Cards\Summary;
17+
use Elegantly\Seo\Twitter\Image as TwitterImage;
18+
19+
class SeoUnifiedData implements Taggable
20+
{
21+
/**
22+
* @param null|Alternate[] $alternates
23+
* @param null|Schema[] $schemas
24+
*/
25+
public function __construct(
26+
public string $title,
27+
public string $canonical,
28+
public ?string $description = null,
29+
public ?string $robots = null,
30+
public ?string $sitemap = null,
31+
public ?array $alternates = null,
32+
public ?Image $image = null,
33+
public ?string $locale = null,
34+
public ?array $schemas = null,
35+
public ?SeoTags $customTags = null,
36+
) {
37+
//
38+
}
39+
40+
public function toTwitter(): Card
41+
{
42+
return new Summary(
43+
title: $this->title,
44+
description: $this->description,
45+
image: $this->image ? new TwitterImage(
46+
url: $this->image->url,
47+
alt: $this->image->alt
48+
) : null,
49+
);
50+
}
51+
52+
public function toOpenGraph(): Vertical
53+
{
54+
return new Website(
55+
title: $this->title,
56+
description: $this->description,
57+
image: $this->image ? new OpenGraphImage(
58+
url: $this->image->url,
59+
type: $this->image->type,
60+
width: $this->image->width,
61+
height: $this->image->height,
62+
alt: $this->image->alt,
63+
) : null,
64+
url: $this->canonical,
65+
locale: $this->locale ? new Locale(
66+
locale: $this->locale,
67+
alternate: array_map(fn (Alternate $alternate) => $alternate->hreflang, $this->alternates ?? []),
68+
) : null,
69+
);
70+
}
71+
72+
public function toStandard(): StandardData
73+
{
74+
return new StandardData(
75+
title: $this->title,
76+
canonical: $this->canonical,
77+
description: $this->description,
78+
robots: $this->robots,
79+
sitemap: $this->sitemap,
80+
alternates: $this->alternates,
81+
);
82+
}
83+
84+
public function toManager(): SeoManager
85+
{
86+
return new SeoManager(
87+
standard: $this->toStandard(),
88+
opengraph: $this->toOpenGraph(),
89+
twitter: $this->toTwitter(),
90+
schemas: $this->schemas,
91+
customTags: $this->customTags,
92+
);
93+
}
94+
95+
public function toTags(): SeoTags
96+
{
97+
return $this->toManager()->toTags();
98+
}
99+
}

tests/Units/OpenGrpahTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@
2525
)->toBe(implode("\n", [
2626
'<meta property="og:type" content="website" />',
2727
'<meta property="og:title" content="Foo" />',
28+
'<meta property="og:url" content="https://example.com/opengraph" />',
2829
'<meta property="og:image" content="https://example.com/opengraph/image" />',
2930
'<meta property="og:image:url" content="https://example.com/opengraph/image" />',
3031
'<meta property="og:image:alt" content="Opengraph example image" />',
31-
'<meta property="og:url" content="https://example.com/opengraph" />',
3232
'<meta property="og:description" content="Bar" />',
3333
'<meta property="og:locale" content="en" />',
3434
'<meta property="og:locale:alternate" content="fr" />',

tests/Units/SeoManagerTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use Elegantly\Seo\Twitter\Image as TwitterImage;
1111

1212
it('renders all standard, opengrpah and twitter tags', function () {
13-
$opengraph = new SeoManager(
13+
$manager = new SeoManager(
1414
standard: new StandardData(
1515
title: 'Foo',
1616
canonical: 'https://example.com/standard',
@@ -51,7 +51,7 @@
5151
);
5252

5353
expect(
54-
$opengraph->toTags()->toHtml()
54+
$manager->toTags()->toHtml()
5555
)->toBe(implode("\n", [
5656
'<title >Foo</title>',
5757
'<meta name="description" content="Bar" />',
@@ -61,10 +61,10 @@
6161
//
6262
'<meta property="og:type" content="website" />',
6363
'<meta property="og:title" content="Foo" />',
64+
'<meta property="og:url" content="https://example.com/opengraph" />',
6465
'<meta property="og:image" content="https://example.com/opengraph/image" />',
6566
'<meta property="og:image:url" content="https://example.com/opengraph/image" />',
6667
'<meta property="og:image:alt" content="Opengraph example image" />',
67-
'<meta property="og:url" content="https://example.com/opengraph" />',
6868
'<meta property="og:description" content="Bar" />',
6969
'<meta property="og:locale" content="en" />',
7070
'<meta property="og:locale:alternate" content="en" />',

tests/Units/SeoUnifiedDataTest.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
use Elegantly\Seo\Standard\Alternate;
4+
use Elegantly\Seo\Unified\Image;
5+
use Elegantly\Seo\Unified\SeoUnifiedData;
6+
7+
it('renders all standard, opengrpah and twitter tags', function () {
8+
$data = new SeoUnifiedData(
9+
title: 'Foo',
10+
canonical: 'https://example.com',
11+
description: 'Bar',
12+
locale: 'en',
13+
alternates: [
14+
new Alternate(
15+
hreflang: 'en',
16+
href: 'https://example.com/en'
17+
),
18+
new Alternate(
19+
hreflang: 'fr',
20+
href: 'https://example.com/fr'
21+
),
22+
],
23+
image: new Image(
24+
url: 'https://example.com/image',
25+
alt: 'Example image'
26+
),
27+
);
28+
29+
expect(
30+
$data->toTags()->toHtml()
31+
)->toBe(implode("\n", [
32+
'<title >Foo</title>',
33+
'<meta name="description" content="Bar" />',
34+
'<link rel="canonical" href="https://example.com" />',
35+
'<link rel="alternate" hreflang="en" href="https://example.com/en" />',
36+
'<link rel="alternate" hreflang="fr" href="https://example.com/fr" />',
37+
//
38+
'<meta property="og:type" content="website" />',
39+
'<meta property="og:title" content="Foo" />',
40+
'<meta property="og:url" content="https://example.com" />',
41+
'<meta property="og:image" content="https://example.com/image" />',
42+
'<meta property="og:image:url" content="https://example.com/image" />',
43+
'<meta property="og:image:alt" content="Example image" />',
44+
'<meta property="og:description" content="Bar" />',
45+
'<meta property="og:locale" content="en" />',
46+
'<meta property="og:locale:alternate" content="en" />',
47+
'<meta property="og:locale:alternate" content="fr" />',
48+
//
49+
'<meta name="twitter:card" content="summary" />',
50+
'<meta name="twitter:title" content="Foo" />',
51+
'<meta name="twitter:description" content="Bar" />',
52+
'<meta name="twitter:image" content="https://example.com/image" />',
53+
'<meta name="twitter:image:alt" content="Example image" />',
54+
]));
55+
});

0 commit comments

Comments
 (0)