Simple PHP library to help developers 🍻 do better on-page SEO optimization

v1.0.2 2019-03-02 17:07 UTC

This package is auto-updated.

Last update: 2021-04-05 00:19:01 UTC


README

Build Status GitHub license Packagist PHP Version Support Packagist Version Twitter

Simple PHP library to help developers 🍻 do better on-page SEO optimization

PHP SEO features:

  • [👷] Generate schema.org ld+json
  • [🛀] Generate meta tags with twitter and open graph support
  • [🗺] Generate sitemaps xml and indexes (supports: 🖺 news, 🖼 images, 📽 videos)
  • [📤] Submit new sitemaps to 🌐 search engines
  • [🙈] No dependencies
  • [🖧] && more coming soon...

Installation:

composer require melbahja/seo

Usage:

Check this simple examples. (of course the composer autoload.php file is required)

👷 Generate schema.org

use Melbahja\Seo\Factory;

$schema = Factory::schema('organization')
            ->url('https://example.com')
            ->logo('https://example.com/logo.png')
                ->contactPoint
                    ->telephone('+1-000-555-1212')
                    ->contactType('customer service');

echo $schema;

Results: (formatted)

<script type="application/ld+json">
{  
   "@context":"https:\/\/schema.org",
   "@type":"Organization",
   "url":"https:\/\/example.com",
   "logo":"https:\/\/example.com\/logo.png",
   "contactPoint":{  
      "@type":"ContactPoint",
      "telephone":"+1-000-555-1212",
      "contactType":"customer service"
   }
}
</script>
use Melbahja\Seo\Factory;

$schema = Factory::schema('book')
            ->name('The Book Name')
            ->url('https://example.com/books/the-book')
            ->author
                ->set('@type', 'Person')
                ->name('J.D. Jhon')
            ->getRoot();

echo json_encode($schema, JSON_PRETTY_PRINT);

Results:

{
    "@context": "https:\/\/schema.org",
    "@type": "Book",
    "name": "The Book Name",
    "url": "https:\/\/example.com\/books\/the-book",
    "author": {
        "@type": "Person",
        "name": "J.D. Jhon"
    }
}
use Melbahja\Seo\Factory;

$schema = Factory::schema('product')
            ->image(['https://example.com/image.jpeg', 'https://example.com/2.jpeg'])
            ->name('The Product Name')
            ->description('Product description...')
            ->sku('12828127112')
            ->brand->set('@type', 'Thing')->name('Brand Name')
            ->getParent()->aggregateRating->ratingValue("4.4")->ratingCount("89")
            ->getParent()->review(
            [
                'reviewRating' => 
                [
                    '@type' => 'Rating',
                    'ratingValue' => '4',
                    'bestRating' => '5'
                ],

                'author' =>
                [
                    '@type' => 'Person',
                    'name' => "Mohamed ELbahja"
                ]
            ])
            ->offers
                ->set('@type', 'AggregateOffer')
                ->lowPrice('119.99')
                ->highPrice('200.99')
                ->priceCurrency('USD')
                ->availability('https://schema.org/InStock')
                ->offerCount('100');

echo $schema;

Results:

<script type="application/ld+json">
{  
   "@context":"https:\/\/schema.org",
   "@type":"Product",
   "image":[  
      "https:\/\/example.com\/image.jpeg",
      "https:\/\/example.com\/2.jpeg"
   ],
   "name":"The Product Name",
   "description":"Product description...",
   "sku":"12828127112",
   "brand":{  
      "@type":"Thing",
      "name":"Brand Name"
   },
   "aggregateRating":{  
      "@type":"AggregateRating",
      "ratingValue":"4.4",
      "ratingCount":"89"
   },
   "review":{  
      "reviewRating":{  
         "@type":"Rating",
         "ratingValue":"4",
         "bestRating":"5"
      },
      "author":{  
         "@type":"Person",
         "name":"Mohamed ELbahja"
      }
   },
   "offers":{  
      "@type":"AggregateOffer",
      "lowPrice":"119.99",
      "highPrice":"200.99",
      "priceCurrency":"USD",
      "availability":"https:\/\/schema.org\/InStock",
      "offerCount":"100"
   }
}
</script>

🛀 Meta Tags

use Melbahja\Seo\Factory;

$metatags = Factory::metaTags(
[
	'title' => 'My new article',
	'description' => 'My new article about how php is awesome',
	'keywords' => 'php, programming',
	'robots' => 'index, nofollow',
	'author' => 'Mohamed Elbahja'
]);

echo $metatags;

Results:

<meta name="title" content="My new article" />
<meta name="description" content="My new article about how php is awesome" />
<meta name="keywords" content="php, programming" />
<meta name="robots" content="index, nofollow" />
<meta name="author" content="Mohamed Elbahja" />
<meta property="twitter:title" content="My new article" />
<meta property="twitter:description" content="My new article about how php is awesome" />
<meta property="og:title" content="My new article" />
<meta property="og:description" content="My new article about how php is awesome" />
use Melbahja\Seo\Factory;

$metatags = Factory::metaTags();

$metatags->meta('author', 'Mohamed Elabhja')
		->meta('title', 'PHP SEO')
		->meta('description', 'This is my description')
		->image('https://avatars3.githubusercontent.com/u/8259014')
		->mobile('https://m.example.com')
		->url('https://example.com')
		->shortlink('https://git.io/phpseo')
		->amp('https://amp.example.com')
		->facebook('prop', 'propValue example og')
		->twitter('prop', 'propValue example twitter');

echo $metatags;

Results:

<meta name="author" content="Mohamed Elabhja" />
<meta name="title" content="PHP SEO" />
<meta name="description" content="This is my description" />
<link rel="alternate" media="only screen and (max-width: 640px)" href="https://m.example.com" />
<link rel="canonical" href="https://example.com" />
<link rel="shortlink" href="https://git.io/phpseo" />
<link rel="amphtml" href="https://amp.example.com" />
<meta property="twitter:title" content="PHP SEO" />
<meta property="twitter:description" content="This is my description" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:image" content="https://avatars3.githubusercontent.com/u/8259014" />
<meta property="twitter:url" content="https://example.com" />
<meta property="twitter:prop" content="propValue example twitter" />
<meta property="og:title" content="PHP SEO" />
<meta property="og:description" content="This is my description" />
<meta property="og:image" content="https://avatars3.githubusercontent.com/u/8259014" />
<meta property="og:url" content="https://example.com" />
<meta property="og:prop" content="propValue example og" />

🗺 Sitemaps

$yourmap = Factory::sitemap(string $url, array $options = []): SitemapIndexInterface
Option name Description Required ? Default
save_path Generated sitemaps storage path YES
sitemaps_url Sitemap index custom url for generated sitemaps NO $url
index_name Custom sitemap index name NO sitemap.xml
Simple Example
use Melbahja\Seo\Factory;

$sitemap = Factory::sitemap('https://example.com', ['save_path' => '/path/to_save/files']);

$sitemap->links('blog.xml', function($map) 
{
    $map->loc('/blog')->freq('daily')->priority('0.8')
        ->loc('/blog/my-new-article')->freq('weekly')->lastMode('2019-03-01')
        ->loc('/اهلا-بالعالم')->freq('weekly');
    $map->loc('/blog/hello')->freq('monthly');
});

// return bool
// throws SitemapException if save_path options not exists
$sitemap->save();

Results: (📂 in: /path/to_save/files/)

📁: sitemap.xml (formatted)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by https://git.io/phpseo -->
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <sitemap>
        <loc>https://example.com/blog.xml</loc>
        <lastmod>2019-03-01T14:38:02+01:00</lastmod>
    </sitemap>
</sitemapindex>

📁: blog.xml (formatted)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by https://git.io/phpseo -->
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
      <loc>https://example.com/blog</loc>
      <changefreq>daily</changefreq>
      <priority>0.8</priority>
    </url>
    <url>
      <loc>https://example.com/blog/my-new-article</loc>
      <changefreq>weekly</changefreq>
      <lastmod>2019-03-01T00:00:00+01:00</lastmod>
    </url>
    <url>
      <loc>https://example.com/%D8%A7%D9%87%D9%84%D8%A7-%D8%A8%D8%A7%D9%84%D8%B9%D8%A7%D9%84%D9%85</loc>
      <changefreq>weekly</changefreq>
    </url>
    <url>
      <loc>https://example.com/blog/hello</loc>
      <changefreq>monthly</changefreq>
    </url>
</urlset>
Multipe Sitemaps && Images
use Melbahja\Seo\Factory;

$sitemap = Factory::sitemap('https://example.com');

// Instead of passing save_path to the factory you can set it later via setSavePath
// also $sitemap->getSavePath() method to get the current save_path
$sitemap->setSavePath('your_save/path');

// changing sitemap index name
$sitemap->setIndexName('index.xml');

// For images you need to pass a option images => true
$sitemap->links(['name' => 'blog.xml', 'images' => true], function($map) 
{
    $map->loc('/blog')->freq('daily')->priority('0.8')
        ->loc('/blog/my-new-article')
            ->freq('weekly')
            ->lastMode('2019-03-01')
            ->image('/uploads/image.jpeg', ['caption' => 'My caption'])
        ->loc('/اهلا-بالعالم')->freq('weekly');

    // image(string $url, array $options = []), image options: caption, geo_location, title, license
    // see References -> images   
    $map->loc('/blog/hello')->freq('monthly')->image('https://cdn.example.com/image.jpeg');
});

// another file
$sitemap->links('blog_2.xml', function($map) 
{
	// Mabye you need to loop through posts form your database ?
	foreach (range(0, 4) as $i)
	{
		$map->loc("/posts/{$i}")->freq('weekly')->priority('0.7');
	}
});

$sitemap->save();

Results

📁: index.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by https://git.io/phpseo -->
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <sitemap>
        <loc>https://example.com/blog.xml</loc>
        <lastmod>2019-03-01T15:13:22+01:00</lastmod>
    </sitemap>
    <sitemap>
        <loc>https://example.com/blog_2.xml</loc>
        <lastmod>2019-03-01T15:13:22+01:00</lastmod>
    </sitemap>
</sitemapindex>

📁: blog.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by https://git.io/phpseo -->
<urlset
    xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
    xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
    <url>
        <loc>https://example.com/blog</loc>
        <changefreq>daily</changefreq>
        <priority>0.8</priority>
    </url>
    <url>
        <loc>https://example.com/blog/my-new-article</loc>
        <changefreq>weekly</changefreq>
        <lastmod>2019-03-01T00:00:00+01:00</lastmod>
        <image:image>
            <image:caption>My caption</image:caption>
            <image:loc>https://example.com/uploads/image.jpeg</image:loc>
        </image:image>
    </url>
    <url>
        <loc>https://example.com/%D8%A7%D9%87%D9%84%D8%A7-%D8%A8%D8%A7%D9%84%D8%B9%D8%A7%D9%84%D9%85</loc>
        <changefreq>weekly</changefreq>
    </url>
    <url>
        <loc>https://example.com/blog/hello</loc>
        <changefreq>monthly</changefreq>
        <image:image>
            <image:loc>https://cdn.example.com/image.jpeg</image:loc>
        </image:image>
    </url>
</urlset>

📁: blog_2.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by https://git.io/phpseo -->
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
        <loc>https://example.com/posts/0</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://example.com/posts/1</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://example.com/posts/2</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://example.com/posts/3</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
    <url>
        <loc>https://example.com/posts/4</loc>
        <changefreq>weekly</changefreq>
        <priority>0.7</priority>
    </url>
</urlset>
Sitemap with videos
$sitemap = Factory::sitemap('https://example.com')
                ->setSavePath('./storage/sitemaps')
                ->setSitemapsUrl('https://example.com/sitemaps')
                ->setIndexName('index.xml');

$sitemap->links(['name' => 'posts.xml', 'videos' => true], function($map) 
{
	$map->loc('/posts/clickbait-video')->video('My Clickbait Video title', 
	[
		// or thumbnail_loc 
		'thumbnail' => 'https://example.com/thumbnail.jpeg',
		'description' => 'My description',
		// player_loc or content_loc one of them is required
		'player_loc' => 'https://example.com/embed/81287127'

		// for all availabe options see References -> videos
	]);

	$map->loc('posts/bla-bla');
});

$sitemap->save();

Results

📁: index.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by https://git.io/phpseo -->
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <sitemap>
        <loc>https://example.com/sitemaps/posts.xml</loc>
        <lastmod>2019-03-01T15:30:02+01:00</lastmod>
    </sitemap>
</sitemapindex>

Note: lastmod in sitemap index files are generated automatically

📁: posts.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by https://git.io/phpseo -->
<urlset
    xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
    xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
    <url>
        <loc>https://example.com/posts/clickbait-video</loc>
        <video:video>
            <video:description>My description</video:description>
            <video:player_loc>https://example.com/embed/81287127</video:player_loc>
            <video:title>My Clickbait Video title</video:title>
            <video:thumbnail_loc>https://example.com/thumbnail.jpeg</video:thumbnail_loc>
        </video:video>
    </url>
    <url>
        <loc>https://example.com/posts/bla-bla</loc>
    </url>
</urlset>
News Sitemaps
use Melbahja\Seo\Factory;

$sitemap = Factory::sitemap('https://example.com',
[
	// You can also customize your options by passing array to the factory like this
	'save_path' => './path',
	'sitemaps_url' => 'https://example.com/maps',
	'index_name' => 'news_index.xml'
]);

$sitemap->news('my_news.xml', function($map) 
{
    // publication: name, language
    // Google quote about the name: "It must exactly match the name as 
    // it appears on your articles on news.google.com"
    $map->setPublication('PHP NEWS', 'en');

    $map->loc('/news/12')->news(
    [
       'title' => 'PHP 8 Released',
       'publication_date' => '2019-03-01T15:30:02+01:00',
    ]);

    $map->loc('/news/13')->news(
    [
        'title' => 'PHP 8 And High Performance',
        'publication_date' => '2019-04-01T15:30:02+01:00'
    ]);
});

$sitemap->save();

Results

📁: news_index.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by https://git.io/phpseo -->
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <sitemap>
        <loc>https://example.com/maps/my_news.xml</loc>
        <lastmod>2019-03-01T15:57:10+01:00</lastmod>
    </sitemap>
</sitemapindex>

📁: my_news.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by https://git.io/phpseo -->
<urlset
    xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
    xmlns:news="https://www.google.com/schemas/sitemap-news/0.9">
    <url>
        <loc>https://example.com/news/12</loc>
        <news:news>
            <news:publication>
                <news:name>PHP NEWS</news:name>
                <news:language>en</news:language>
            </news:publication>
            <news:title>PHP 8 Released</news:title>
            <news:publication_date>2019-03-01T15:30:02+01:00</news:publication_date>
        </news:news>
    </url>
    <url>
        <loc>https://example.com/news/13</loc>
        <news:news>
            <news:publication>
                <news:name>PHP NEWS</news:name>
                <news:language>en</news:language>
            </news:publication>
            <news:title>PHP 8 And High Performance</news:title>
            <news:publication_date>2019-04-01T15:30:02+01:00</news:publication_date>
        </news:news>
    </url>
</urlset>

Google quote: ⚠ "If you submit your News sitemap before your site has been reviewed and approved by our team, you may receive errors." ⚠

🤖 Send Sitemaps To Search Engines

According to the sitemaps protocol, search engines should have a url that allow you to inform them about your new sitemap files. like: <searchengine_URL>/ping?sitemap=sitemap_url

use Melbahja\Seo\Factory;

// the void method send() will inform via CURL: google, bing and yandex about your new file
Factory::ping()->send('https://example.com/sitemap_file.xml');

TODOs:

New features coming in v1.1

  • Add robots.txt builder
  • Add validation for image options
  • Add support for video restriction
  • Add more tests
  • Add a simple integration for frameworks (🍮cakephp and 🔦laravel)
  • Add a better documentation
  • Your suggestions [Open new issue 🤔]

References

License:

MIT Copyright (c) 2019 Mohamed Elbahja