stfalcon/blog-bundle
StfalconBlogBundle adds a ready-to-use blog to Symfony apps, with posts, categories, tags, comments, and an admin interface for content management. It plugs in via standard Symfony bundle configuration, giving you a simple, customizable blogging layer without building one yourself.
First, accept that this is not installable in a Laravel app. stfalcon/blog-bundle is a Symfony2 bundle: no service provider, no publishable config, no Blade views, no Eloquent models. composer require stfalcon/blog-bundle will fail on php and symfony/* constraints; forcing it with --ignore-platform-reqs gives you a bundle Laravel never boots. So "day-to-day" use means one thing: mine it as a feature spec and reference implementation while building your own Laravel module.
Where to look first, in order:
composer.json — pins the era. This tells you the whole package is EOL-coupled (Symfony 2.x, Twig 1.x, Doctrine 2.x, PHP 5.x). Do not try to resolve it.Entity/ — Post, Category/Tag, the Post↔Tag join, Comment. This is the only genuinely reusable artifact: the schema.Controller/ — admin CRUD and the frontend index/show actions. Read these to enumerate the actual endpoints and routing behavior.Resources/views/ — Twig templates for index, show, tags, breadcrumbs, pagination, and the Disqus widget.Resources/config/ — DI service definitions and routing. Not portable, but tells you which bundles were hard dependencies (StofDoctrineExtensionsBundle for sluggable/timestampable, KnpPaginatorBundle for pagination are the usual suspects — verify).First use case: a 2–3 day spike. Read the above and produce (a) a one-page feature inventory and (b) a data-model diagram. Then decide: build native vs. adopt a maintained Laravel blogging package vs. headless CMS. Default recommendation is build native — the scope is small and only the schema is worth copying.
Your Laravel starting schema, derived from the spike:
// posts: id, slug (unique), title, body, excerpt, published_at, author_id, timestamps
// tags: id, slug (unique), name
// post_tag: post_id, tag_id (composite PK)
// index: unique(slug), (published_at, id) for keyset/cursor pagination
1. Port the domain, not the framework. Translate Doctrine annotations → Eloquent models and relationships. The bundle's value here is that it answers "what does a simple blog actually cost?" — posts, categories/tags, many-to-many tagging, cut/read-more, syntax highlighting, breadcrumbs, pagination, comments. That list is a credible spec you can write tickets against.
2. Read-more parsing — decide the contract explicitly. The bundle supports a "cut and read more" marker in the body. In Laravel, resolve this in the model:
public function getExcerptAttribute(): string
{
return $this->attributes['excerpt']
?? Str::of($this->body)->before('<!--more-->')->stripTags();
}
Do not rely on a delimiter you haven't documented. If you need an explicit excerpt column, add it — the bundle's ambiguity here is exactly the kind of thing to nail down rather than inherit.
3. Admin CRUD → Laravel resource controllers. The bundle's admin screens map cleanly to Route::resource('posts', ...), Form Requests for validation, and Gate/Policy checks for authorization. You gain things the bundle never offered (middleware-based auth, validation error bags) by not porting its form types.
4. Public index/show → paginated queries with eager loading. The bundle's archive pages are the classic N+1 trap for per-post tags. In Laravel:
Post::query()
->whereNotNull('published_at')->where('published_at', '<=', now())
->with('tags') // prevents N+1
->latest('published_at')
->paginate(15);
5. Slugs — replicate sluggable behavior deliberately. The bundle delegates to Stof's sluggable extension. If you have bulk-imported content, generate slugs with Str::slug() and handle collisions (append -2, -3, or fall back to a short id). Make published slugs immutable to avoid breaking URLs.
6. Comments — make this an explicit product decision, not an inherited default. The bundle ships Disqus. That's the 2013 default. For a Laravel module: prefer a first-party comments table (moderation, data ownership, SEO) unless legal/ops explicitly sign off on Disqus with consent gating. This is the single highest-judgment area of the port.
7. Render pipeline for bodies and code blocks. Adopt the bundle's intent — rich HTML bodies plus highlighted code — but not its assets. Use a maintained server-side highlighter (e.g. a modern PHP highlighter) or a self-hosted client library, and pass rendered HTML through HTML Purifier. This is non-negotiable given author-supplied HTML.
8. Breadcrumbs and tag archives. Simple Post belongsToMany Tag plus a /tags/{tag:slug} route. Keep the bundle's SEO-friendly tag page structure but generate structured data yourself — it won't be in the templates.
9. Acceptance checklist from the tests. Run the bundle's test scenarios as behavior descriptions ("post index paginates at N", "draft posts are excluded from the public index", "tag page lists only published posts tagged with X"). They encode intended behavior for free.
--ignore-platform-reqs; the bundle boot lifecycle (Bundle::boot(), registerContainerConfiguration()) has no Laravel analogue, and the bundle will silently no-op if forced in.->with('tags') and paginate at 10–20 items.(published_at, id) so pagination doesn't table-scan as content grows.view()->exists() fallbacks or a published-views convention — reimplement the intent, not the mechanism.How can I help you explore Laravel packages today?