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

Secretary Bundle Laravel Package

secretary/secretary-bundle

SecretaryBundle packages the Secretary library for Symfony, providing service configuration, dependency injection, and sensible defaults so you can integrate its features into PHP applications with minimal setup and clear extension points. It streamlines installation and keeps configuration flexible...

View on GitHub
Deep Wiki
Context7

Getting Started

Secretary is a lightweight markup language (a Markdown-like syntax with its own .sct conventions) and this bundle is the thin Symfony integration around php-secretary. Treat it as a markup-rendering service, not as a full CMS.

  1. Install the parser and the bundle

    composer require secretary/secretary-bundle
    

    The bundle depends on secretary/php-secretary, so the core parser comes along with it.

  2. Register the bundle

    • On a Flex-based app it is typically picked up automatically, but confirm it is not disabled in config/bundles.php.
    • On older (pre-Flex) apps you register it manually in app/AppKernel.php inside registerBundles().
  3. Look at the bundle's DI extension first. There is essentially no user documentation for a 2-star bundle. The authoritative reference is src/SecretaryBundle/DependencyInjection/*Extension.php (or config/ on newer releases) plus the Twig extension class. That tells you the service id, the config tree, and the filter name.

  4. First use case — render stored markup in a template. The canonical day-to-day pattern is: content is authored as .sct (in a CMS field, a file, or a Doctrine column), and the bundle exposes a Twig filter so the template stays dumb:

    {{ article.body|secretary }}
    

    The filter runs the Secretary parser and returns an HTML string. Because it returns markup, follow it with |raw if your Twig config does not already treat it as safe HTML.

  5. First use case without Twig — inject the parser service into a controller or service and compile manually:

    $html = $this->secretary->compile($markup);
    

    The exact method name depends on the php-secretary version pinned in your composer.lock; check the vendor class once and wrap it behind your own adapter (see below).

Do not go looking for a bin/console command or an editor integration — the bundle is purely a service/filter bridge.

Implementation Patterns

Wrap the parser behind your own small service. This bundle is read-only and unmaintained. Do not reference its service id from 40 places across the codebase. Define one App\Markup\Renderer that depends on the Secretary service, exposes render(string $markup): string, and let everything else depend on that. If you later swap to CommonMark, league/commonmark, or Parsedown, you change one class.

final class MarkupRenderer
{
    public function __construct(private object $secretary) {}

    public function render(string $markup): string
    {
        return $this->secretary->compile($markup);
    }
}

Cache compiled HTML, not the markup. Parsing is cheap-ish per document but pointless to repeat on every request for immutable content. Cache the rendered HTML keyed by a hash of the source (plus a parser version key), not just by content id — otherwise edits to the markup serve stale HTML.

$key = 'sct:'.hash('xxh3', $markup);
return $this->cache->get($key, fn () => $this->renderer->render($markup));

Store both forms if you need search. The parser gives you HTML; it does not give you a plain-text index. If you need search, strip tags at write time and store a content_text column alongside the raw .sct. Do the stripping in a Doctrine lifecycle listener so it stays consistent.

Do the transform on write for CMS-like content, on read for user input. Two valid pipelines:

  • Write-time rendering (blog posts, docs): render once in a Doctrine prePersist/preUpdate listener or a messenger handler, persist body_html, serve it directly. Cheapest at request time.
  • Read-time rendering (previews, comments): render in the controller/service just before output. Necessary when you must show unsaved edits.

Twig usage — keep the filter at the leaf. Prefer {{ article.body|secretary|raw }} only where you control the input. If markup can come from untrusted users, route it through HTML sanitisation first (e.g. twig/html-extra or an HTML purifier), because Secretary will happily emit whatever the markup produces.

Integrate with asset/static-site tooling via a command. For a static site or docs export, write a small console command that walks .sct files, calls the renderer, and writes .html to the output dir. This is where a bundle with no commands still earns its keep — it wires the parser correctly for you.

Configuration is intentionally thin. Expect a config tree exposing the parser options (extensions/features). Set them once under config/packages/secretary.yaml; do not scatter options across environments. If the config key is not obvious, dump the container: bin/console debug:container secretary --show-arguments.

Compose with the rest of Symfony. The parser is pure PHP with no request context, so it is safe to call from Messenger workers, commands, and API controllers alike. Put the rendering call in a service, not in a controller action, so it can be reused by the async path.

Gotchas and Tips

  • The bundle is abandoned. [READ-ONLY], 2 stars, 0 dependents. No security patches, no PHP 8.x/Symfony 6+ guarantees. Pin the version in composer.json and plan an exit strategy (usually migrating to league/commonmark). Do not build a product-critical pipeline on it without an owner.
  • Secretary is not CommonMark. Do not assume Markdown files render identically. Nested lists, tables, footnotes, and reference links may differ or be unsupported. Test your actual corpus early; a diff against league/commonmark output is the fastest way to find divergence.
  • Output is raw HTML. There is no sanitisation step in the bundle. Every |secretary on user-controlled input is an XSS vector until you sanitise. Escaping (|e) after the filter destroys the markup, so sanitise before rendering or accept only trusted content.
  • |raw is often required. If your Twig config escapes by default, forgetting |raw shows literal <p> tags. Conversely, if the bundle marks the output as safe HTML, adding |raw is harmless but the "safe" marking is a trust decision you should verify in the Twig extension source.
  • Service id and method names are undocumented. Confirm both once (bin/console debug:container | grep -i secretary and a glance at vendor/secretary/php-secretary/src/) and record them in the wrapper service. Guessing from README examples of other versions is a common time sink.
  • No Flex recipe. Auto-registration usually works, but config defaults and bundle registration may need manual scaffolding. If debug:container shows nothing, the bundle is not registered.
  • Parsing errors are silent or throw late. Feed the parser a malformed document early in development to see whether it throws or degrades; wrap the renderer call in a try/catch that logs and falls back to escaped source text so a bad edit never white-screens a page.
  • No streaming or incremental parsing. Whole-document transforms only; very large documents block the request. Offload big files to Messenger and render on write.
  • Caching key must include parser version. When you upgrade (or if you fork) php-secretary, bump the cache namespace or old HTML will survive the fix.
  • Extension points live in the library, not the bundle. Custom syntax goes through php-secretary's extension mechanism and must be registered with the parser service — usually by decorating the service or defining a compiler pass. The bundle exposes no first-class "add extension" config in most versions.
  • Audit before adopting. With zero dependents, there is no community signal on edge cases. Run your own fixture suite: a dozen representative .sct files, snapshot the HTML, and treat any change as a regression.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin