Publino-Blog in PHP — sauber & SEO-stark
Ob pures PHP, WordPress oder Laravel: Mit composer require
binden Sie Publino-Inhalte in jedes Projekt ein. Zero runtime dependencies, PHP 8.2+.
<?php
// config/publino.php
require_once __DIR__ . '/vendor/autoload.php';
use Publino\Sdk\PublinoClient;
// Named Arguments (PHP 8.2) machen die Konfiguration selbsterklärend.
$publino = new PublinoClient(
baseUrl: 'https://api.publino.de',
siteId: getenv('PUBLINO_SITE_ID'),
apiKey: getenv('PUBLINO_API_KEY'),
); Per Composer installieren
Den API-Key erzeugen Sie im Dashboard unter API & MCP → Delivery Keys.
Nutzen Sie den Secret Key (pub_live_…) ausschließlich serverseitig — in PHP ist das automatisch der Fall.
ext-curl und ext-json. Ein eigener Transport lässt sich über das Transport-Interface einsetzen.
# Composer (PHP 8.2+)
composer require publino/sdk
# Zero-Runtime-Dependencies. Benötigt nur ext-curl und ext-json
# (beide in jeder Standard-PHP-Installation enthalten). # .env — Secret Key nur serverseitig verwenden
PUBLINO_API_KEY=pub_live_xxxxxxxxxxxxxxxx
PUBLINO_SITE_ID=mein-blog Client zentral konfigurieren
Legen Sie den Client einmal an und binden Sie ihn überall ein. Die Keys kommen aus Umgebungsvariablen — niemals hartcodiert ins Repository.
baseUrl ist standardmäßig https://api.publino.de.
<?php
// config/publino.php
require_once __DIR__ . '/vendor/autoload.php';
use Publino\Sdk\PublinoClient;
// Named Arguments (PHP 8.2) machen die Konfiguration selbsterklärend.
$publino = new PublinoClient(
baseUrl: 'https://api.publino.de',
siteId: getenv('PUBLINO_SITE_ID'),
apiKey: getenv('PUBLINO_API_KEY'),
); Die Blog-Übersicht bauen
$publino->articles('de') gibt ein Array typisierter ArticleSummary-Objekte zurück. Zugriff über Eigenschaften ($a->title); beim Ausgeben immer htmlspecialchars() nutzen.
<?php
// blog.php — Übersicht aller Artikel
require __DIR__ . '/config/publino.php';
// articles() gibt ein Array von ArticleSummary-Objekten zurück (neueste zuerst).
$articles = $publino->articles('de');
?>
<!doctype html>
<html lang="de">
<head><meta charset="utf-8"><title>Blog</title></head>
<body>
<h1>Blog</h1>
<div class="grid">
<?php foreach ($articles as $a): ?>
<a href="/blog/<?= $a->slug ?>-<?= $a->id ?>" class="card">
<h2><?= htmlspecialchars($a->title) ?></h2>
<?php if ($a->excerpt): ?>
<p><?= htmlspecialchars($a->excerpt) ?></p>
<?php endif; ?>
<small><?= $a->readingMinutes ?> Min. Lesezeit</small>
</a>
<?php endforeach; ?>
</div>
</body>
</html> Einzelartikel mit perfektem SEO
$publino->article($ref, 'de') liefert ein Article-Objekt (oder null bei 404) — inklusive metaDescription, canonicalUrl, fertigem jsonLd und sauberem html. Den Body geben Sie bewusst ungefiltert aus.
<?php
// artikel.php — Einzelartikel mit perfektem SEO
require __DIR__ . '/config/publino.php';
// Referenz aus der URL: "{slug}-{publicId}" oder die bloße Public-ID.
$ref = $_GET['ref'] ?? '';
$article = $publino->article($ref, 'de'); // ?Article (null = nicht gefunden)
if ($article === null) {
http_response_code(404);
exit('Artikel nicht gefunden');
}
$desc = $article->metaDescription ?? $article->excerpt ?? '';
$canonical = $article->canonicalUrl ?? "https://example.com/blog/$ref";
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title><?= htmlspecialchars($article->title) ?></title>
<!-- 1. Meta-Description & Canonical -->
<meta name="description" content="<?= htmlspecialchars($desc) ?>">
<link rel="canonical" href="<?= htmlspecialchars($canonical) ?>">
<!-- 2. Open Graph -->
<meta property="og:type" content="article">
<meta property="og:title" content="<?= htmlspecialchars($article->title) ?>">
<meta property="og:description" content="<?= htmlspecialchars($desc) ?>">
<!-- 3. Schema.org JSON-LD — fertig von Publino mitgeliefert -->
<?php if ($article->jsonLd !== null): ?>
<script type="application/ld+json">
<?= json_encode($article->jsonLd, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>
</script>
<?php endif; ?>
</head>
<body>
<article>
<h1><?= htmlspecialchars($article->title) ?></h1>
<p><small><?= $article->readingMinutes ?> Min. Lesezeit</small></p>
<!-- 4. Body: server-gerendertes, bereinigtes HTML aus Publino -->
<?= $article->html ?>
</article>
</body>
</html> Als WordPress-Shortcode
Publino-Inhalte direkt in WordPress — ohne den Editor zu wechseln. Der Shortcode [publino_blog] rendert die Liste, ein WordPress-Transient cached die Antwort.
<?php
/**
* Publino-Blog als Shortcode -> [publino_blog]
* functions.php oder eigenes Plugin
*/
require_once get_stylesheet_directory() . '/vendor/autoload.php';
use Publino\Sdk\PublinoClient;
function publino_client(): PublinoClient {
return new PublinoClient(
baseUrl: 'https://api.publino.de',
siteId: 'mein-blog',
apiKey: getenv('PUBLINO_API_KEY'),
);
}
add_shortcode('publino_blog', function () {
// WordPress-Transient als Cache (15 Minuten) — spart API-Calls.
$articles = get_transient('publino_articles');
if ($articles === false) {
$articles = publino_client()->articles('de');
set_transient('publino_articles', $articles, 15 * MINUTE_IN_SECONDS);
}
ob_start();
foreach ($articles as $a) {
printf(
'<article><a href="/blog/%s-%s"><h2>%s</h2></a><p>%s</p></article>',
esc_attr($a->slug),
esc_attr($a->id),
esc_html($a->title),
esc_html($a->excerpt ?? '')
);
}
return ob_get_clean();
}); Controller & Blade-View
Sauber getrennt nach MVC: Der Controller lädt die Daten (mit Laravel-Cache), die Blade-View kümmert sich um Markup und SEO-Head.
<?php
// app/Http/Controllers/BlogController.php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
use Publino\Sdk\PublinoClient;
class BlogController extends Controller
{
private function publino(): PublinoClient
{
return new PublinoClient(
baseUrl: 'https://api.publino.de',
siteId: config('services.publino.site'),
apiKey: config('services.publino.key'),
);
}
public function index()
{
// 10 Minuten cachen über Laravels Cache-Layer.
$articles = Cache::remember('publino.articles', 600, fn () =>
$this->publino()->articles('de')
);
return view('blog.index', compact('articles'));
}
public function show(string $ref)
{
$article = $this->publino()->article($ref, 'de');
abort_if($article === null, 404);
return view('blog.show', compact('article'));
}
} {{-- resources/views/blog/show.blade.php --}}
@section('head')
<title>{{ $article->title }}</title>
<meta name="description" content="{{ $article->metaDescription ?? '' }}">
<link rel="canonical" href="{{ $article->canonicalUrl }}">
@if ($article->jsonLd !== null)
<script type="application/ld+json">
{!! json_encode($article->jsonLd, JSON_UNESCAPED_SLASHES) !!}
</script>
@endif
@endsection
<article class="prose">
<h1>{{ $article->title }}</h1>
{{-- HTML aus Publino bewusst ungefiltert ausgeben --}}
{!! $article->html !!}
</article> Cachen & per Webhook invalidieren
Cachen Sie Antworten (Transient/Cache) und löschen Sie den Cache gezielt, sobald Publino einen Webhook sendet — so bleibt der Blog schnell und aktuell.
<?php
// Cache invalidieren per Publino-Webhook (article.published / .updated)
// webhook.php
$payload = json_decode(file_get_contents('php://input'), true);
if (in_array($payload['event'] ?? '', ['article.published', 'article.updated'], true)) {
// WordPress:
delete_transient('publino_articles');
// Laravel:
// Cache::forget('publino.articles');
}
http_response_code(200); Lieber JavaScript oder ganz ohne Backend?
Für Next.js, Astro & Nuxt gibt es das JS-SDK — und für reine HTML-Seiten den embed.js-Weg.