Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Manual Bundle Laravel Package

opifer/manual-bundle

A Symfony bundle for building and managing online manuals and documentation. It provides structured manual pages, content management, and integration with Opifer's CMS, letting teams organize help content and publish searchable documentation from within their application.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first: opifer/manual-bundle is a Symfony 2 bundle, archived, 3 stars, zero dependents. It is not a Laravel package and cannot be composer required into a modern Laravel or Symfony 4+ app. Treat it as a read-only reference implementation — the day-to-day value is the schema, route shape, and authoring flow, not the code.

Where to look first (5-minute orientation):

  1. Resources/config/routing.yml — the actual URL surface (list, show, likely an admin CRUD prefix). This is the contract to replicate.
  2. Entity/ (or Model/) — the article schema: title, slug, body (Markdown), category/order, timestamps, publish state. This is the durable artifact.
  3. Controller/ — how list/detail are fetched and rendered; note pagination and ordering behavior.
  4. Resources/views/ — the Twig templates. These tell you the UX shape (index listing, breadcrumb/nav, article body render).
  5. composer.json — pins the Symfony 2 generation and the Markdown renderer version. This is your XSS/abandonment signal.

First use case — Laravel reimplementation (recommended path):

composer require league/commonmark
php artisan make:model HelpArticle -m
php artisan make:controller HelpController
  • Migration: title, slug (unique), body (longText, raw Markdown), category_id (nullable), position, published_at (nullable), timestamps.
  • Model: route-key slug, scopePublished().
  • HelpController@index / @show with MarkdownConverter configured in safe mode (html_input: 'strip', allow_unsafe_links: false) and cached per-article (Cache::remember("help.{$slug}", ...)).
  • Admin: Filament/Nova resource over the same model — do not port the Twig CRUD.

First use case — legacy Symfony 2 install (only if you genuinely maintain one):

// app/AppKernel.php
new Opifer\ManualBundle\OpiferManualBundle(),
# app/config/routing.yml
opifer_manual:
    resource: "@OpiferManualBundle/Resources/config/routing.yml"
    prefix: /manual

Then doctrine:schema:update --dump-sql, run fixtures if shipped, and hit /manual. Pin the vendor (fork into a private repo) rather than depending on the remote.

The first thing you ship in either path is a single published article end-to-end: DB row → route → sanitized Markdown → HTML. Everything else (categories, ordering, admin) is incremental.

Implementation Patterns

1. Schema mirroring (the core pattern). The bundle's contract is portable: article body stored as raw Markdown, rendered at read time. Keep it that way — storing rendered HTML forces a data migration every time you fix a formatting bug or tighten sanitization. In Eloquent:

protected $casts = ['published_at' => 'datetime'];
public function scopePublished($q) { return $q->whereNotNull('published_at')->where('published_at', '<=', now()); }
public function getRouteKeyName() { return 'slug'; }

2. Render pipeline as a dedicated service, not a controller concern.

class ArticleRenderer {
    public function __construct(private MarkdownConverter $md) {}
    public function render(HelpArticle $a): string {
        return Cache::remember("help.html.{$a->id}.{$a->updated_at->timestamp}",
            3600, fn() => $this->md->convert($a->body)->getContent());
    }
}

This gives you one place to swap renderers, add sanitization, or add a table-of-contents post-processor. Cache key includes updated_at so edits invalidate naturally.

3. Admin CRUD as a thin resource over the model. Don't reuse the bundle's Twig controllers. A Filament resource (or Nova) with:

  • Markdown textarea + live preview (client-side marked/micromark in the form only)
  • A status select: draft / published / scheduled — this is the biggest gap versus the reference bundle, which has no real workflow
  • Slug auto-generated on create, editable, unique-validated

4. Public routes as a contract test. The bundle exposes list + detail. Replicate exactly:

Route::get('/manual', [HelpController::class, 'index'])->name('help.index');
Route::get('/manual/{article:slug}', [HelpController::class, 'show'])->name('help.show');

Write one feature test per route that asserts 200, the title appears in the response, and an unpublished article 404s. Then use the same tests as the acceptance criteria when comparing against the legacy app.

5. Content migration from the legacy bundle. Export id, title, slug, body, category, created_at, updated_at to JSON via a one-off script against the legacy DB. Import with updateOrCreate keyed on slug. Run a golden-file parity check: render N real articles through old and new pipelines, diff the HTML (normalize whitespace/attribute order), and gate cutover on zero structural diffs.

6. Search (the thing the bundle doesn't have). If you're replacing it, add it immediately rather than later: either Postgres tsvector over title + body or a WHERE title LIKE ? fallback for SQLite/dev. The index page should have a query box; the reference bundle's lack of it is the most visible user-facing gap.

7. Integration tip — contextual help. Once articles exist, call them from elsewhere in the app: HelpArticle::published()->where('category', 'billing')->get() rendered in a slide-over on the billing page. This is where in-app help earns its keep; the reference bundle has no hook for it, so design the query surface first.

Gotchas and Tips

  • The Symfony 2 trap. Every composer require opifer/manual-bundle in a modern app will either fail resolution or pull Symfony 2 components that conflict with your symfony/* constraints. Do not spend an afternoon fighting it "just to see" — the outcome is always a fork with rewritten namespaces. Read the source, don't install it.

  • Archived means you are the maintainer. No CVE path, no PHP 8.4 fix, no issue triage. If you do vendor the legacy code, document that in an ADR ("reference-derived, private fork, not upstream-tracked") so a future engineer doesn't try to composer update it.

  • The Markdown renderer is your XSS surface. The bundle pins an old renderer; if articles are ever authored by non-trusted users (or imported from user submissions), that's stored XSS. In the reimplementation, hard-require league/commonmark safe mode and add a test that <script> and javascript: links are stripped.

  • Render at read time, not write time. Covered above, but it's the single most common mistake when cloning this pattern — people cache rendered HTML in the body column and then can't fix formatting without a batch job.

  • No drafts / no scheduling in the reference bundle. Its schema implies a simple publish flag. If you mirror that naively you'll bolt on status and published_at later with a migration. Add them day one — it costs nothing.

  • Breadcrumbs aren't a real hierarchy. "Hierarchical manual pages" in the description means ordering/grouping, not a nested-set tree. Don't over-engineer with parent_id adjacency unless you have a confirmed need for sub-articles; a flat category + position covers the observed use case.

  • Extension point that actually transfers: template override. In the reference bundle, Twig overrides in app/Resources/OpiferManualBundle/views/ let you restyle without forking. In Laravel, the equivalent is resources/views/vendor/help/* — publish the views (php artisan vendor:publish) and you keep the same "style without touching logic" discipline.

  • Debugging checklist when an article won't render: (1) is published_at set and in the past? (2) does the slug route-key collide? (3) is the rendered HTML cached under a stale updated_at? (4) did the Markdown sanitizer strip what you expected (code fences inside HTML blocks are a classic silent drop)? Add a ?nocache=1 dev guard to bypass the render cache.

  • Estimate anchor: a competent Laravel dev ships the full replacement (model, migration, renderer, index, show, admin resource, search, tests) in under a week. If your estimate exceeds that, you're rebuilding the bundle rather than its pattern — stop and reconsider what you actually need.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core