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

Rain Laravel Package

october/rain

October Rain is the core library behind October CMS, a modular content management system built on Laravel. It provides the module and plugin architecture, theme engine, backend scaffolding, and CMS features developers use to build and extend custom websites and applications.

View on GitHub
Deep Wiki
Context7

Getting Started

october/rain is the foundational library behind October CMS — not a standalone framework. It packages the pieces October itself is built from: a dynamic extension system, a file-backed ORM (Halcyon), Eloquent model traits, a wildcard event dispatcher, parsers, a cURL HTTP client, and a set of helpers that extend Laravel's Str/Arr.

Install

composer require october/rain

If you are already running October CMS, stop — Rain is a dependency of the CMS and is installed and wired for you. Installing it separately in that project will cause version conflicts.

If you are evaluating it for a plain Laravel app, check composer.json first. Rain pins a specific Illuminate major version; installing it will constrain or downgrade your Laravel components. Treat it as "I want October's building blocks" rather than "I want a utility package."

Where to look first

Path What's inside
src/Extension/ExtendableTrait.php The behavior/mixin system — the single most reused piece
src/Halcyon/ File-based models (front-matter Markdown → objects)
src/Database/Traits/ Validation, Purgeable, Sortable, NestedTree, Revisionable
src/Events/ Wildcard + priority event dispatcher
src/Parse/ Yaml, Markdown, Blade, Bracket parsers
src/Network/Http.php Small cURL wrapper
src/Support/ Str, Arr, ServiceProvider, facades

First use case — add behavior to a class without touching it

This is the pattern you will use every day in October plugin work: extend a class from the outside instead of forking it.

use October\Rain\Extension\ExtendableTrait;

class Report
{
    use ExtendableTrait;

    protected $extensionHidden = ['extensionData'];

    public function __construct()
    {
        $this->extendableConstruct();
    }

    public function __get($name)         { return $this->extendableGet($name); }
    public function __set($name, $value) { $this->extendableSet($name, $value); }
    public function __call($name, $args) { return $this->extendableCall($name, $args); }
    public static function __callStatic($name, $args) { return static::extendableCallStatic($name, $args); }
}

// Somewhere else — a plugin's boot(), a service provider, a test
Report::extend(function (Report $report) {
    $report->addDynamicProperty('currency', 'USD');
    $report->addDynamicMethod('total', fn () => 100);
});

$report = new Report;
echo $report->total();     // 100
echo $report->currency;    // USD

That single mechanism is why Rain exists: it lets plugins and themes mutate core classes without editing vendor code.

Implementation Patterns

Extending core classes from a plugin

In October, the canonical place is Plugin.php::boot(), because extensions registered there run after all plugins have registered:

public function boot()
{
    \RainLab\User\Models\User::extend(function ($model) {
        $model->addDynamicMethod('isAdmin', fn () => $model->hasRole('admin'));
    });
}

Prefer Event::listen() when you only need to react to something; reserve extend() for adding methods, properties, or behaviors that other code will call directly.

Halcyon: file-backed models

Halcyon gives you Eloquent-shaped objects whose storage is a directory of files with YAML front matter. This is how October stores CMS templates, partials, and content files.

use October\Rain\Halcyon\Model;

class Post extends Model
{
    protected $dirName    = 'content/posts';
    protected $datasource = 'content';
    protected $fillable   = ['title', 'slug', 'body'];
}

On disk:

---
title: Hello world
slug: hello-world
---
# Hi

Usage:

$post = new Post;
$post->title = 'Hello world';
$post->slug  = 'hello-world';
$post->body  = '# Hi';
$post->save();

$post = Post::find('hello-world');
$recent = Post::orderBy('published_at', 'desc')->limit(5)->get();

Standalone, you must register a datasource yourself (October's CMS module does this for you):

use October\Rain\Halcyon\Datasource\FileDatasource;
use October\Rain\Halcyon\DatasourceManager;
use October\Rain\Halcyon\Model;

$datasources = new DatasourceManager;
$datasources->addDatasource('content', new FileDatasource(base_path('content')));

Model::setDatasourceResolver($datasources);

Database model traits

October\Rain\Database\Model extends Eloquent and layers on opt-in traits. The Validation trait is the highest-value one — it turns model rules into a self-validating model:

use October\Rain\Database\Model;
use October\Rain\Database\Traits\Validation;

class Product extends Model
{
    use Validation;

    public $rules = [
        'name'  => 'required|between:1,255',
        'price' => 'required|numeric|min:0',
    ];

    public $attributeNames = ['name' => 'Product name'];

    protected $guarded = [];
}

try {
    (new Product(['name' => '', 'price' => -1]))->validate();
} catch (\October\Rain\Exception\ValidationException $e) {
    $errors = $e->getErrors(); // MessageBag
}

Other traits worth reaching for: Purgeable (declare cache keys, call purge() on save), Sortable (drag-and-drop ordering columns), NestedTree (parent_id / nest_left / nest_right), Revisionable (audit history — requires the October table).

Events: wildcards and priorities

Rain's dispatcher supports wildcard names and integer priorities (higher runs first). Wildcard listeners receive the event name as the first argument — a signature difference that trips people up:

Event::listen('shop.order.paid', fn ($order) => ..., 10);

Event::listen('shop.order.*', function ($eventName, $payload) {
    // $payload is the array passed to fire()
});

Event::fire('shop.order.paid', [$order]);

For your own classes, use the Emitter trait instead of wiring a dispatcher:

use October\Rain\Support\Traits\Emitter;

class Cart { use Emitter; }

$cart->bindEvent('checkout', fn () => $this->recalculate());
$cart->fireEvent('checkout');

Parsers

use October\Rain\Parse\Yaml;
use October\Rain\Parse\Markdown;

$config = Yaml::parse($yamlString);
$html   = Markdown::parse($markdownString);

Parse\Blade renders a Blade string against an array (useful for user-editable email templates); Parse\Bracket is a deliberately small {variable} / {% if %} engine for non-developer-editable content.

HTTP client

use October\Rain\Network\Http;

$response = Http::get('https://api.example.com/users')
    ->header('Accept', 'application/json')
    ->timeout(15)
    ->send();

if ($response->code === 200) {
    $users = $response->json();
}

Http::post('https://hooks.slack.com/services/...', ['text' => 'Deploy complete'])->send();
Http::get($pdfUrl)->toFile(storage_path('app/report.pdf'));

Writing config back to disk

For settings screens, October\Rain\Config\FileWriter serializes an array back into a PHP config file. It is a blunt instrument — see the gotcha below.

Gotchas and Tips

It is not a general-purpose package. Rain is the internal library of a CMS. The NOASSERTION license field and zero Dependents on Packagist reflect that the repo is a read-only split of a monorepo — not a signal of abandonment. Confirm the actual license upstream (October CMS is MIT) before shipping.

Version alignment is the #1 integration hazard. Rain pins Illuminate majors to match the October release it belongs to. In a non-October app, Composer will either refuse to install or silently downgrade your framework. Never upgrade Rain independently of the CMS.

ExtendableTrait silently fails if you forget the magic methods. If you don't implement __get, __set, __call, __callStatic, and call extendableConstruct() in the constructor, dynamic methods simply won't resolve — no error, just Call to undefined method.

Extensions only apply to future instances. Report::extend(...) callbacks run during construction. In long-running workers (queue, Octane), classes extended after boot won't affect already-resolved singletons. October provides clearExtendedClasses(); in your own code, register extensions in a service provider's register(), not lazily.

Plugin boot order matters. Two plugins extending the same class will layer in registration order. Use boot() for extensions, register() for bindings, and prefer events over direct extend() when you don't control ordering.

Halcyon is not a database. No transactions, no foreign keys, no eager loading. The query surface is a narrow subset of Eloquent (where, orderBy, limit, get, first). Writes go straight to disk: watch file permissions (the web user must own the content directory), concurrent writes, and rename/move operations — there is no migration tool, so relocating $dirName means writing a script.

Halcyon caches file reads. If you modify files outside the model (deploy, git pull, sync job), the in-memory cache can serve stale content until cleared — in October, php artisan cache:clear handles it.

Catch ValidationException yourself. In the October backend the framework converts it into a redirect with flashed errors. In a custom controller or job, an uncaught exception is a 500. Catch it and return 422 / $e->getErrors().

Purgeable is only as good as your cache store. If you aren't using a taggable cache driver, purge() can't target regions and effectively does nothing useful. Verify your cache driver before relying on cache invalidation for correctness.

Config\FileWriter flattens your config. It writes return [...] with var_export-style output — comments vanish and env() calls get evaluated and baked in as literals. Never write back a config file that contains env() references, or you'll pin production secrets into a committed file.

The HTTP client is cURL with a fluent wrapper. No middleware, no retry policy, no PSR-18. For anything involving retries, circuit breaking, or mocking in tests, reach for Guzzle or Laravel's Http facade instead.

Str and Arr are supersets of Laravel's. October's versions add helpers (Str::getClassId(), Arr::sortRecursive(), etc.), but the import path differs. Code copied between October plugins and vanilla Laravel will break on the use statement, not the call.

Tests are the documentation. There is essentially no standalone usage guide for Rain. The tests/ directory in the repo is the de facto API reference — read it before guessing at a method signature.

Debugging tips. In October, trace_log() writes to the system log with a backtrace and is far more useful than dd() inside an event listener. For dispatcher problems, temporarily register Event::listen('*', fn ($name) => logger($name)) to see what is actually firing and in what order.

Extension points worth knowing. addDynamicMethod and addDynamicProperty are the sanctioned hooks for adding behavior; static::extend() is the sanctioned hook for installing it. Reach for those before reaching for a subclass — subclasses won't be picked up by code that has already resolved the parent class.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle