Skip to content

Commit

Permalink
WIP keep using fluent methods now based on CustomSchema class
Browse files Browse the repository at this point in the history
  • Loading branch information
ralphjsmit committed Jun 15, 2024
1 parent 0743d45 commit e73fe3c
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 204 deletions.
137 changes: 78 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,10 @@ class Homepage extends Controller
This package can also **generate any structured data** for you (also called schema markup).
Structured data is a very vast subject, so we highly recommend you to check the [Google documentation dedicated to it](https://developers.google.com/search/docs/appearance/structured-data/search-gallery).

Structured data can be added in two ways:
- Construct custom arrays of the structured data format, which is then rendered in JSON with the correct tags on the right place by the package.
- Use one of the 2 pre-defined templates (`Article` and `BreadcrumbList`).

### Adding your first schema

Let's add the FAQPage schema markup to our website as an example:
Expand All @@ -343,41 +347,39 @@ public function getDynamicSEOData(): SEOData
{
return new SEOData(
// ...
schema: SchemaCollection::make()->add(fn(SEOData $SEOData) => [
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => [
'@type' => 'Question',
'name' => 'Your question goes here',
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => 'Your answer goes here',
schema: SchemaCollection::make()
->add(fn (SEOData $SEOData) => [
// You could use the `$SEOData` to dynamically
// use any data about the current page.
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => [
'@type' => 'Question',
'name' => 'Your question goes here',
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => 'Your answer goes here',
],
],
],
]),
]),
);
}
```

> [!TIP]
> When adding a new schema, you can check the [documentation here](https://developers.google.com/search/docs/appearance/structured-data/faqpage) to know what keys to add.
> [!TIP]
> After generating the structured data, it is always a good idea to [test your website with Google's rich result validator](https://search.google.com/test/rich-results).
### Pre-configured Schema: Article and BreadcrumbList

To help you get started with structured data, we added 2 preconfigured schema:
To help you get started with structured data, we added 3 preconfigured schema that you can modify using fluent methods. The following types are available:

1. `Article`
2. `BreadcrumbList`
3. `FAQPage`

### Article schema markup

You can add a pre-configured article with the `withArticle` method, this will generate a fully filled Article JSON schema using the values from your `SEOData` instance.

> [!NOTE]
> Check the Google documentation about [Article](https://developers.google.com/search/docs/appearance/structured-data/article)
To enable structured data, you need to use the schema property of the SEOData class. To automatically generate `Article` schema markup, use the `->addArticle()` method:

```php

Expand All @@ -387,14 +389,16 @@ public function getDynamicSEOData(): SEOData
{
return new SEOData(
// ...
schema: SchemaCollection::make()->withArticle(),
schema: SchemaCollection::make()
->addArticle(),
);
}
```

You can also pass a closure to the `->withArticle()` method to customize the individual schema markup.
You can pass a closure to `->addArticle()` method to customize the individual schema markup. This closure will receive an instance of ArticleSchema as its argument. You can an additional author by using the `->addAuthor()` method:

```php
use RalphJSmit\Laravel\SEO\Schema\ArticleSchema;
use RalphJSmit\Laravel\SEO\SchemaCollection;
use RalphJSmit\Laravel\SEO\Support\SEOData;
use Illuminate\Support\Collection;
Expand All @@ -404,21 +408,25 @@ public function getDynamicSEOData(): SEOData
return new SEOData(
// ...
title: "A boring title"
schema: SchemaCollection::make()->withArticle(function(SEOData $SEOData, Collection $article){
return $article->mergeRecursive([
'alternativeHeadline' => "Not {$SEOData->title}", // will be "Not A boring title"
'author' => [
[
'@type' => 'Person',
'name' => $this->moderator,
]
]
]);
}),
schema: SchemaCollection::make()
->addArticle(function (ArticleSchema $article, SEOData $SEOData): ArticleSchema {
return $article
->addAuthor($this->moderator)
->markup(function (Collection $markup) use ($SEOData) : Collection {
return $markup
->put('alternativeHeadline', "Not {$SEOData->title}") // Set/overwrite alternative headline property to `Will be "Not A boring title"` :)
->mergeRecursive([
//...
]);
});
}),
);
}
```

> [!TIP]
> Check the Google documentation about [Article](https://developers.google.com/search/docs/appearance/structured-data/article) for more information.
### BreadcrumbList schema markup

You can also add `BreadcrumbList` schema markup by using the `->withBreadcrumbList()` function on the `SchemaCollection`.
Expand All @@ -430,33 +438,19 @@ use RalphJSmit\Laravel\SEO\SchemaCollection;
use RalphJSmit\Laravel\SEO\Support\SEOData;
use Illuminate\Support\Collection;

SchemaCollection::make()
->withBreadcrumbList(function (SEOData $SEOData, Collection $breadcrumbs) {
$items = $breadcrumb->get('itemListElement', []);

$breadcrumb->put(
'itemListElement',
[
[
'@type' => 'ListItem',
'name' => 'Homepage',
'item' => 'https://example.com',
],
[
'@type' => 'ListItem',
'name' => 'Category',
'item' => 'https://example.com/test',
],
...$items,
[
'@type' => 'ListItem',
'name' => 'Subarticle',
'item' => 'https://example.com/test/article/2',
]
],
);

return $breadcrumb;
SchemaCollection::initialize()
->addBreadcrumbs(function (BreadcrumbListSchema $breadcrumbs, SEOData $SEOData): BreadcrumbListSchema {
return $breadcrumbs
->prependBreadcrumbs([
'Homepage' => 'https://example.com',
'Category' => 'https://example.com/test',
])
->appendBreadcrumbs([
'Subarticle' => 'https://example.com/test/article/2',
])
->markup(function (Collection $markup): Collection {
// ...
});
});
```

Expand All @@ -467,6 +461,31 @@ This code will generate `BreadcrumbList` JSON-LD structured data with the follow
3. [Current page]
4. Subarticle

> [!TIP]
> Check the Google documentation about [BreadcrumbList](https://developers.google.com/search/docs/appearance/structured-data/breadcrumb) for more information.
### FAQPage schema markup

You can also add FAQPage schema markup by using the ->addFaqPage() function on the SchemaCollection:

```php
use RalphJSmit\Laravel\SEO\Schema\FaqPageSchema;
use RalphJSmit\Laravel\SEO\SchemaCollection;use RalphJSmit\Laravel\SEO\Support\SEOData;

SchemaCollection::initialize()
->addFaqPage(function (FaqPageSchema $faqPage, SEOData $SEOData): FaqPageSchema {
return $faqPage
->addQuestion(name: "Can this package add FaqPage to the schema?", acceptedAnswer: "Yes!")
->addQuestion(name: "Does it support multiple questions?", acceptedAnswer: "Of course.");
});
```

> [!TIP]
> Check the Google documentation about [Faq Page](https://developers.google.com/search/docs/appearance/structured-data/faqpage) for more information.
> [!TIP]
> After generating the structured data, it is always a good idea to [test your website with Google's rich result validator](https://search.google.com/test/rich-results).
## Advanced usage

Sometimes you may have advanced needs that require you to apply your own logic to the `SEOData` class, just before it is used to generate the tags.
Expand Down
21 changes: 8 additions & 13 deletions src/Schema/ArticleSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
namespace RalphJSmit\Laravel\SEO\Schema;

use Carbon\CarbonInterface;
use Closure;
use Illuminate\Support\Collection;
use Illuminate\Support\HtmlString;
use RalphJSmit\Laravel\SEO\Support\SEOData;

/**
* @deprecated Use CustomSchema paradigm
*/
class ArticleSchema extends Schema
class ArticleSchema extends CustomPreDefinedSchema
{
public array $authors = [];

Expand All @@ -32,7 +29,7 @@ class ArticleSchema extends Schema

public function addAuthor(string $authorName): static
{
if (empty($this->authors)) {
if ( ! $this->authors) {
$this->authors = [
'@type' => 'Person',
'name' => $authorName,
Expand All @@ -52,7 +49,7 @@ public function addAuthor(string $authorName): static
return $this;
}

public function initializeMarkup(SEOData $SEOData, array $markupBuilders): void
public function initializeMarkup(SEOData $SEOData): void
{
$this->url = $SEOData->url;

Expand All @@ -66,22 +63,22 @@ public function initializeMarkup(SEOData $SEOData, array $markupBuilders): void
];

foreach ($properties as $markupProperty => $SEODataProperty) {
if ($SEOData->{$SEODataProperty}) {
if ( $SEOData->{$SEODataProperty} ) {
$this->{$markupProperty} = $SEOData->{$SEODataProperty};
}
}

if ($SEOData->author) {
if ( $SEOData->author ) {
$this->authors = [
'@type' => 'Person',
'name' => $SEOData->author,
];
}
}

public function generateInner(): HtmlString
public function generateInner(): Collection
{
$inner = collect([
return collect([
'@context' => 'https://schema.org',
'@type' => $this->type,
'mainEntityOfPage' => [
Expand All @@ -98,7 +95,5 @@ public function generateInner(): HtmlString
->when($this->articleBody, fn (Collection $collection): Collection => $collection->put('articleBody', $this->articleBody))
->pipeThrough($this->markupTransformers)
->toJson();

return new HtmlString($inner);
}
}
14 changes: 5 additions & 9 deletions src/Schema/BreadcrumbListSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

namespace RalphJSmit\Laravel\SEO\Schema;

use Closure;
use Illuminate\Support\Collection;
use Illuminate\Support\HtmlString;
use RalphJSmit\Laravel\SEO\Support\SEOData;

/**
* @deprecated Use CustomSchema paradigm
*/
class BreadcrumbListSchema extends Schema
class BreadcrumbListSchema extends CustomPreDefinedSchema
{
public Collection $breadcrumbs;

Expand All @@ -24,16 +22,16 @@ public function appendBreadcrumbs(array $breadcrumbs): static
return $this;
}

public function initializeMarkup(SEOData $SEOData, array $markupBuilders): void
public function initializeMarkup(SEOData $SEOData): void
{
$this->breadcrumbs = collect([
$SEOData->title => $SEOData->url,
]);
}

public function generateInner(): HtmlString
public function generateInner(): Collection
{
$inner = collect([
return collect([
'@context' => 'https://schema.org',
'@type' => $this->type,
'itemListElement' => $this->breadcrumbs
Expand All @@ -48,8 +46,6 @@ public function generateInner(): HtmlString
])
->pipeThrough($this->markupTransformers)
->toJson();

return new HtmlString($inner);
}

public function prependBreadcrumbs(array $breadcrumbs): static
Expand Down
36 changes: 36 additions & 0 deletions src/Schema/CustomPreDefinedSchema.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace RalphJSmit\Laravel\SEO\Schema;

use Closure;
use Illuminate\Support\Collection;
use RalphJSmit\Laravel\SEO\Support\SEOData;

abstract class CustomPreDefinedSchema extends CustomSchema
{
public array $markupTransformers = [];

public function __construct(SEOData $SEOData, array $markupBuilders = [])
{
$this->initializeMarkup($SEOData);

// `$markupBuilders` are closures that modify this schema
// tag object and can call methods on it to change items...
foreach ($markupBuilders as $markupBuilder) {
$markupBuilder($this, $SEOData);
}

parent::__construct($this->generateInner());
}

abstract public function initializeMarkup(SEOData $SEOData): void;

abstract public function generateInner(): Collection;

public function markup(Closure $transformer): static
{
$this->markupTransformers[] = $transformer;

return $this;
}
}
Loading

0 comments on commit e73fe3c

Please sign in to comment.