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

Forte Laravel Package

fortephp/forte

Forte is a Laravel package that provides a clean, expressive API for common application needs, with sensible defaults, flexible configuration, and seamless framework integration. It helps developers reduce boilerplate, ship features faster, and keep their codebase organized and maintainable.

Deep Wiki
Context7

Getting Started

Install and confirm the shape of the API first.

composer require fortephp/forte

The package is a parser plus AST toolkit, not a runtime feature — there is usually nothing to configure before you can use it. If a service provider is auto-discovered, check whether it exposes a config file (php artisan vendor:publish) for things like custom directive registration or parser options.

Before writing any code, spend fifteen minutes in three places in the repo:

  1. src/ root — identify the entry-point class (the thing that turns a Blade string into a tree) and the node base class. In a package like this the pipeline is almost always Parser → Node tree → Printer/Compiler, with Visitor classes for traversal.
  2. tests/ — the test suite is the real documentation here. Look for round-trip tests (parse → print equals input); they tell you what the authors consider safe to mutate.
  3. The node classes — count them. That inventory is your honest feature list; it tells you which Blade constructs you can reason about structurally versus which arrive as opaque "raw" nodes.

Your first use case should be read-only. Don't start by rewriting templates. Start with analysis, which is where a Blade AST pays for itself immediately:

// Pseudocode — confirm exact names in src/
$ast = $parser->parse(file_get_contents(resource_path('views/home.blade.php')));

$visitor = new class extends NodeVisitor {
    public array $includes = [];

    public function enterNode(Node $node): void
    {
        if ($node instanceof IncludeNode) {
            $this->includes[] = $node->getPath();
        }
    }
};

$traverser->addVisitor($visitor)->traverse($ast);

A tiny artisan command that walks resources/views/**/*.blade.php and dumps every @include/@extends/@section is the fastest way to learn the tree shape while producing something genuinely useful: a template dependency graph, an unused-partial report, or a list of views that bypass your component conventions.

Implementation Patterns

Parse once, walk many times. The expensive part is lexing and parsing; traversal is cheap. In a scanning command, read and parse each file a single time, then run several visitors over the same tree rather than re-parsing per rule. If you're running this in CI over hundreds of views, memoize parsed ASTs to a cache directory keyed by file hash — Blade files change far less often than CI runs.

Analysis rules as visitors, one rule per class. The pattern that scales is: each lint or convention check is a NodeVisitor that collects (file, line, message) findings and never mutates. This makes rules independently testable — feed a hand-written Blade snippet, assert the findings — and lets you enable/disable rules from config. Mutating rules should be a separate, clearly-labelled pass.

Codmods: mutate, then prove the round trip. For rewrites (migrating @include to <x-...> components, normalising directive spacing, extracting inline translations), the discipline is:

  1. Parse the original.
  2. Record the printed output without mutations and diff it against the source. If this isn't empty, your mutation diff is unreliable — whitespace and raw-PHP nodes are usually the culprits.
  3. Apply the visitor, print, and review the diff.
  4. Run the app's Blade compilation (php artisan view:cache) as a smoke test before committing.

Do rewrites on a branch, one template directory at a time. A codemod that touches 400 views and breaks four of them is much harder to bisect than four batches of 100.

Static analysis over the compiled output is the wrong layer. Always point the parser at .blade.php source files, never at storage/framework/views/*.php. The compiled files have already lost the directive structure you're trying to inspect, and their names are opaque hashes.

Integration points that work well:

  • A blade:lint artisan command registered in CI alongside Pint/PHPStan.
  • A pre-commit hook running the linter only on staged *.blade.php files, so it stays under a second.
  • A blade:graph command emitting DOT or Mermaid from the include/extends relationships — useful for spotting orphaned partials and circular includes.
  • Generating documentation: extract section names, component props, and slot usage per view into a Markdown reference.

Register your custom directives with the parser. If the app defines Blade::directive('money', ...), the parser needs to know about it or it will treat it as unknown/raw. Find the config or registration hook and feed it the same list the app registers in a service provider — ideally by extracting that list into one shared class both sides read.

Gotchas and Tips

Blade is not context-free, and no parser fully hides that. Directives appearing inside HTML attribute strings, inside <script> blocks, or inside JavaScript template literals are the classic failure mode. Alpine and Vue syntax is the worst offender — @click, {{ }}, and x-data collide directly with Blade's sigils. Expect to handle these as special cases; check whether the package preserves them as raw nodes or mangles them. Write a test with a realistic Alpine-heavy component early, before you trust the tool on your codebase.

Escaped output matters. @{{ ... }} (literal, for JS frameworks) and @@directive (escaped Blade directive) must survive round-tripping unchanged. If your printer emits {{ ... }} for an escaped expression, you've silently introduced a server-side evaluation into a client-side template — a security-relevant bug, not a formatting nit.

Don't assume the AST is lossless. Many Blade parsers retain raw source for constructs they don't model. That's fine for analysis but dangerous for rewriting: if you reconstruct a node from parsed properties rather than mutating in place, you may drop comments, whitespace, or @php blocks nested inside it. Prefer in-place mutation of leaf nodes and leave everything you don't understand untouched.

Pin the version. A 67-star package with a recent release date and a score this low is almost certainly pre-1.0 or moving fast. Node class names and visitor signatures are exactly the kind of API that churns. Commit a specific version in composer.json and check the changelog before any composer update that touches it. If you build tooling on it, wrap the package behind your own thin adapter (one class exposing parseFile(): Ast and print(Ast): string) so a breaking change costs you one file, not fifty.

Debugging an unexpected tree: dump the AST for the smallest failing snippet rather than the whole view, and print node class names with their line numbers. Most "the parser is broken" reports resolve to "the parser modelled this as a RawNode and my visitor only handles DirectiveNode." Check the node type before assuming the visitor ran.

Watch for silent no-ops. If a visitor's enterNode type hint doesn't match the actual node class — or the traverser requires void returns and you return a node expecting replacement semantics — your codemod will run cleanly and change nothing. Always assert a non-zero mutation count in the command output.

Performance ceiling. Parsing is CPU-bound and single-threaded. For large view trees, chunk the file list and run several processes (or spatie/fork / laravel/prompts-driven batches) rather than trying to parallelise inside PHP. Parse-time caching is the bigger win; do that first.

Where to extend rather than fork: custom node types (if the package exposes a node registry), the visitor interface, and the directive-registration config are the intended seams. If you find yourself subclassing the lexer, that's a signal the package doesn't model your case — file an issue or vendor a fork deliberately, because lexer internals are the least stable surface.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource