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

Storm Laravel Package

winter/storm

Winter Storm is the Laravel-based core library behind Winter CMS, providing the module and plugin system, backend scaffolding, and shared helpers that power the platform. Use it to build or extend Winter CMS applications on a familiar, well-documented foundation.

View on GitHub
Deep Wiki
Context7

Getting Started

Storm is not a "require it and read the docs" library — it's the substrate under Winter CMS. So the first step is deciding which Storm you're using:

If you're on Winter CMS already: Storm is in your dependency tree. You don't install it; you learn it. Start by listing what's available so you stop reinventing helpers:

ls vendor/winter/storm/src/            # module inventory
cat vendor/winter/storm/src/Filesystem/README.md   # per-module API catalogue

Each src/<Module>/ has a README. Read Filesystem first — it's the highest-value module and the one whose absence from your mental model causes the worst bugs.

If you're on plain Laravel and want it anyway: don't start with composer require winter/storm. Start with a throwaway spike that only touches static helpers. Confirm three things before committing:

  1. composer require winter/storm resolves against your PHP/Laravel constraints.
  2. Autoloading doesn't collide — Storm is a CMS substrate and may register global function files, facades, or aliases that clash with yours. A name collision here is a fatal boot error in every environment, not a warning.
  3. PathResolver and Filesystem are usable once autoloaded, without the Winter application class.

If those three fail, vendor the classes instead (MIT permits it, with attribution) and skip the dependency graph.

First use case — the one that pays for the read time immediately:

use Winter\Storm\Filesystem\PathResolver;

// Instead of realpath() + null-check + trim
$real = PathResolver::resolve($userSuppliedPath);

// Instead of str_starts_with($path, $root) — which is prefix-collision-broken
if (!PathResolver::within($real, $uploadsRoot)) {
    abort(403);
}

within() is separator-boundary aware after the GHSA-58fp fix. That means /var/www/secret_other correctly fails containment against /var/www/secret. The hand-rolled version of that check passes — and that's the class of bug that ends up in a CVE.

Day-one checklist:

  • Read src/Filesystem/README.md and skim PathResolver's source.
  • Read agents/AGENTS.md (bundled with the repo). It's the search-order policy: Storm → Laravel → Symfony, never hand-roll. Adopt it whether or not you adopt the code.
  • Pin an exact version. Storm tracks Winter's roadmap, and a stale pin retains the path-containment defect.
  • Add Storm to your "check before upgrading Laravel" list.

Implementation Patterns

The substitution table is the daily workflow. Treat it as a lookup, not documentation. Before writing a helper, grep:

grep -rl 'function <thing-you-need>' \
  vendor/winter/storm/src/ \
  vendor/laravel/framework/src/ \
  vendor/symfony/

Ten seconds against a hundred-line mistake. The patterns that come up most in day-to-day work:

Path containment on any user-supplied path. File managers, theme editors, plugin uploaders, media libraries — anywhere a path crosses a trust boundary:

$full = PathResolver::join($base, $relative);
$real = PathResolver::resolve($full);          // open_basedir-safe, non-existent-path-safe

if (!PathResolver::within($real, $base)) {
    throw new \RuntimeException('Path escapes base');
}

This is the pattern. resolve() then within() then use the resolved path — never the raw input, and never re-derive after the check (TOCTOU: resolve once, use that resolution).

Cross-platform path handling. If you ship to Windows or your CI is Linux while prod is mixed:

$fs = new Winter\Storm\Filesystem\Filesystem();

$fs->isAbsolutePath($p);   // covers Unix, Windows drive letters, and URL schemes — better than str_starts_with('/')
$fs->normalizePath($p);    // backslash → forward slash, for comparison
$fs->symbolizePath($p);    // ~/ and $/ symbol expansion
PathResolver::standardize($p);  // forward slash → DIRECTORY_SEPARATOR, for OS APIs

Separate the two concerns: normalizePath on comparison, standardize on handoff to the filesystem.

Recursive directory ops. Storm's Filesystem extends Illuminate's, so you get both:

$fs = new Winter\Storm\Filesystem\Filesystem();
$fs->deleteDirectory($tmp);       // Storm/Illuminate
$fs->existsInsensitive($p);       // Storm addition — case-insensitive existence check

Atomic writes are not here — reach for Symfony's Filesystem::dumpFile(), which Storm's own internals rely on for the same reason you should (partial-write races on flat-file stores):

(new \Symfony\Component\Filesystem\Filesystem())->dumpFile($path, $contents);

Finding files. Don't write RecursiveIteratorIterator + RegexIterator chains:

Finder::create()->files()->name('*.less')->in($dir)

Flat-file models (Halcyon). Only if you have a concrete requirement — this is where Storm's CMS container assumptions live, and where a plain Laravel app starts inheriting abstractions it doesn't need. If you do use it, the atomic-write and single-node constraints are yours to manage: flat-file models have no transactional semantics across distributed writers, and cache invalidation is your problem. Do not put Halcyon behind a horizontally scaled write path without a design review.

Contract tests as the integration pattern. Every Storm upgrade should be gated by behaviour you already asserted. This is the day-to-day discipline that makes the dependency safe:

public function test_path_containment()
{
    config(['open_basedir' => null]);
    $root = '/var/www/secret';
    $this->assertFalse(PathResolver::within('/var/www/secret_other', $root)); // prefix collision
    $this->assertFalse(PathResolver::within('/var/www/secret/../../etc/passwd', $root)); // traversal
    $this->assertTrue(PathResolver::within('/var/www/secret/uploads/a.txt', $root));
    // plus: symlink escape, Windows separators
}

Four tests. Ten minutes to write. Catches the exact failure mode you pin the package to avoid.

Gotchas and Tips

str_starts_with($path, $root) is the bug, not the fix. This is the single most important line in the assessment. A prefix check passes /var/www/secret_other against /var/www/secret. PathResolver::within() is separator-boundary safe after the GHSA-58fp fix — meaning an outdated Storm pin is a live path-traversal risk. Pin exactly, commit the lockfile, and watch Winter's advisories. The package's strongest argument (canonical, audited helpers) is also its strongest argument for pinning.

Bootstrapping is not a documented path. Storm expects a Winter/October-style application container — provider registration, class loader, facade aliases. Registering it inside a plain Laravel app must be proven in a spike, not assumed. The pure static helpers (PathResolver, the non-container-dependent Filesystem methods) are likely fine once autoloaded; Halcyon/Argon/Database model extensions are where the container assumptions bite. Decide early: sliver, or framework marriage.

Autoload collisions are fatal, not warnings. If Storm's composer.json autoloads global function files and one collides with yours, every environment dies at boot. Ask this before Phase 0, not during.

Dependents: 0 is probably a Packagist artefact — Winter's own manifest should depend on Storm. Don't read it as reassurance, and don't read it as a bug either. The real point stands: standalone consumer base is effectively nil. There's no external bug-finder pool, no semver promise you can lean on in a contract, and security advisories appear only if Winter's maintainers file them. Treat it as first-party Winter internals with a bus factor near one.

Respect the layering rule. Storm explicitly forbids depending on Winter modules or plugin code. Using it without that discipline inverts the architecture and you get a shared layer that quietly imports the app. The AGENTS.md search order (Storm → Laravel → Symfony) is a policy, not just docs — it's the thing that keeps duplicated helper logic out of your codebase.

Don't half-adopt the substrate. If you find yourself wanting themes and plugins, stop bolting Storm onto your SaaS and evaluate Winter CMS proper. The worst outcome is a Laravel app carrying a CMS's domain abstractions for two utility classes.

Vendoring is a legitimate exit. If the need is essentially PathResolver, copy it with MIT attribution and take the same contract tests. You trade an external release cadence for a couple hundred lines you own. The hard rule: it must be a copy of the canonical code, never a reimplementation — the whole point is that hand-rolled path logic is where the bugs live.

Windows and open_basedir diverge from Linux CI. If you ship to Windows hosts, run the contract tests cross-platform. standardize() vs normalizePath() confusion usually shows up exactly here.

Debugging tip. When a containment check fails in production but passes locally, print both the resolved path and the root, then compare byte-for-byte — trailing separators, symlink resolution, and case sensitivity (via existsInsensitive) are the three usual culprits. PathResolver::resolve() output is the only string you should be comparing.

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.
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
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core