ljonesfl / blahg
A largely ridiculous blog implementation.
0.5.2
2025-08-11 17:19 UTC
Requires
- php: 8.*.*
- league/commonmark: ^2.0
- suin/php-rss-writer: ^1.6
- symfony/yaml: ^6.4
Requires (Dev)
- phpunit/phpunit: ^9
README
A lightweight PHP library for creating blog applications with Markdown content and YAML metadata.
Features
- Markdown-based content - Write articles in Markdown with GitHub-flavored markdown and footnote support
- YAML metadata - Organize articles with YAML descriptors containing title, tags, categories, and more
- Draft management - Control article visibility with draft status
- RSS feed generation - Automatic RSS feed creation for your blog
- Flexible querying - Filter articles by tag, category, author, or slug
- Future publishing - Schedule articles with future publication dates
Requirements
- PHP 8.4 or higher
- Composer
Installation
Install via Composer:
composer require ljonesfl/blahg
Usage
Basic Setup
use Blahg\Repository; // Initialize repository with your content directory $repository = new Repository('/path/to/articles'); // Get all published articles $articles = $repository->getArticles();
Article Structure
Each article consists of two files:
- YAML descriptor (e.g.,
my-article.yaml
) - Markdown content (e.g.,
my-article.md
)
YAML Descriptor Example
title: "10 Reasons Why I Love Broccoli" slug: "10-reasons-why-i-love-broccoli" datePublished: "2018-12-27" category: "Food" author: "John Doe" description: "A deep dive into the wonderful world of broccoli" tags: - broccoli - vegetables - nutrition path: "10-reasons-broccoli.md" draft: false githubFlavored: true canonicalUrl: "https://example.com/blog/10-reasons-why-i-love-broccoli"
Required Fields
title
- Article titleslug
- URL-friendly identifierdatePublished
- Publication date (YYYY-MM-DD format)path
- Path to the Markdown content file
Optional Fields
category
- Article categorytags
- Array of tagsauthor
- Article authordescription
- Short description for meta tagsdraft
- Set totrue
to hide from public viewgithubFlavored
- Enable GitHub-flavored MarkdowncanonicalUrl
- Canonical URL for SEO
Retrieving Articles
// Get all published articles (excludes drafts and future posts) $articles = $repository->getArticles(); // Get a specific article by slug try { $article = $repository->getArticle('10-reasons-why-i-love-broccoli'); } catch( ArticleNotFound $e ) { // Handle missing article } catch( ArticleMissingBody $e ) { // Handle missing content file } // Include drafts in results $repository->setShowDrafts( true ); $allArticles = $repository->getArticles();
Filtering Articles
// Get articles by category $foodArticles = $repository->getArticlesByCategory( 'Food' ); // Get articles by tag $broccoliArticles = $repository->getArticlesByTag( 'broccoli' ); // Get articles by author $johnsArticles = $repository->getArticlesByAuthor( 'John Doe' );
Getting Metadata
// Get all unique categories $categories = $repository->getCategories(); // Get all unique tags $tags = $repository->getTags(); // Get all unique authors $authors = $repository->getAuthors();
Rendering Articles
Article List Example
<h1>Blog Articles</h1> <?php foreach( $articles as $article ): ?> <article> <h2> <a href="/blog/<?= htmlspecialchars($article->getSlug()) ?>"> <?= htmlspecialchars($article->getTitle()) ?> </a> </h2> <?php if ($article->getDescription()): ?> <p><?= htmlspecialchars($article->getDescription()) ?></p> <?php endif; ?> <div class="meta"> <?php if ($article->getCategory()): ?> <span>Category: <?= htmlspecialchars($article->getCategory()) ?></span> <?php endif; ?> <?php if ($article->getAuthor()): ?> <span>By: <?= htmlspecialchars($article->getAuthor()) ?></span> <?php endif; ?> <time><?= $article->getDatePublished() ?></time> </div> <?php if( $article->getTags() ): ?> <div class="tags"> <?php foreach( $article->getTags() as $tag ): ?> <span class="tag">#<?= htmlspecialchars( $tag ) ?></span> <?php endforeach; ?> </div> <?php endif; ?> </article> <?php endforeach; ?>
Single Article Example
<?php try { $article = $repository->getArticleBySlug( $slug ); ?> <article> <h1><?= htmlspecialchars( $article->getTitle() ) ?></h1> <div class="meta"> <?php if ($article->getAuthor()): ?> <span>By <?= htmlspecialchars( $article->getAuthor() ) ?></span> <?php endif; ?> <time><?= $article->getDatePublished() ?></time> </div> <div class="content"> <?= $article->getBody() ?> </div> <?php if( $article->getTags()) : ?> <div class="tags"> <?php foreach( $article->getTags() as $tag ): ?> <a href="/blog/tag/<?= urlencode( $tag ) ?>"> #<?= htmlspecialchars( $tag ) ?> </a> <?php endforeach; ?> </div> <?php endif; ?> </article> <?php } catch( ArticleNotFound $e ) { echo "<p>Article not found.</p>"; } catch( ArticleMissingBody $e ) { echo "<p>Article content is missing.</p>"; } ?>
RSS Feed Generation
// Generate RSS feed $rssFeed = $repository->getRss( title: 'My Blog', link: 'https://example.com/blog', description: 'A blog about various topics' ); // Output RSS feed header('Content-Type: application/rss+xml; charset=utf-8'); echo $rssFeed;
Exception Handling
The library throws specific exceptions for different error conditions:
ArticleNotFound
- Thrown when requesting a non-existent articleArticleMissingBody
- Thrown when the Markdown file referenced in the YAML descriptor is missingArticleMissingData
- Thrown when required YAML fields are missing
Testing
Run the test suite:
composer test
Run tests with coverage:
composer test-coverage
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.