ca-santos / laravel-support
General-purpose, domain-organized helper library for Laravel applications
Requires
- php: ^8.2
- illuminate/support: ^9.0|^10.0|^11.0|^12.0
Requires (Dev)
- greenlion/php-sql-parser: ^4.4
- inertiajs/inertia-laravel: ^0.6|^1.0|^2.0
- larastan/larastan: ^2.0|^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0|^12.0
Suggests
- greenlion/php-sql-parser: Required to use CaueSantos\Support\Helpers\SqlHelper::getExpressionByAlias().
- inertiajs/inertia-laravel: Required to use CaueSantos\Support\Helpers\LaravelHelper::forceInertiaRedirect().
README
Laravel Support
A collection of small, dependency-light helpers for things almost every Laravel
project ends up writing by hand: string/array/date/number formatting, casting
request-style input, safe redirects, money and enum utilities, building
Eloquent eager-load constraints, generating migrations at runtime, and
hydrating plain PHP objects from arrays (including nested objects, Carbon
dates, Collections and enums).
Everything is a plain static call — no service container bindings, no
facades to fake in tests, no configuration to publish. This package only adds
what Laravel doesn't already give you: nothing here duplicates a native
Str, Arr, Number, or Eloquent method — where one already exists, use it
directly instead.
use CaueSantos\Support\Helpers\StringHelper; use CaueSantos\Support\Helpers\ArrayHelper; use CaueSantos\Support\Helpers\DateHelper; StringHelper::toSnakeSlug('My Cool Title'); // "my_cool_title" ArrayHelper::isListOfArrays([['id' => 1], ['id' => 2]]); // true DateHelper::formatUkDate('2024-03-05'); // "05/03/2024"
Table of contents
- Requirements
- Installation
- How this package is organized
- Helpers
- Object hydration
- Model traits (
HasSlug,HasMetadata) InteractsWithEnum- Makeable objects (
IsMakeable,Component) - Runtime migration generation
- Optional dependencies
- Testing & code quality
- Security
- License
Requirements
- PHP 8.2+
illuminate/support9.x through 12.x (works with Laravel 9 through 12)
Installation
composer require ca-santos/laravel-support
The service provider (CaueSantos\Support\ServiceProvider) is auto-discovered
by Laravel — there is nothing to register or publish.
How this package is organized
Every class named XyzHelper lives under CaueSantos\Support\Helpers and
exposes only public static methods — use the class and call it, nothing
to instantiate:
use CaueSantos\Support\Helpers\StringHelper; StringHelper::randomStr(12);
A handful of classes live directly under CaueSantos\Support because they
aren't stateless utility helpers — traits meant to be used on your own
classes, small value objects, or tools with their own constructor/state
(object hydration, migration generation). Those are documented in their own
sections below.
StringHelper
CaueSantos\Support\Helpers\StringHelper — string generation and formatting
that Laravel's own Str class doesn't cover.
use CaueSantos\Support\Helpers\StringHelper; StringHelper::toSnakeSlug('My Cool Title'); // "my_cool_title" StringHelper::toSnakeSlug('My Cool Title', '-'); // "my-cool-title" StringHelper::randomStr(10); // e.g. "qQqqOaTImp" (letters only — Str::random() mixes in digits) StringHelper::lowerRandomStr(16); // e.g. "wowzultbfpokgxaf" StringHelper::formatTags('Hello {{name}}!', ['name' => 'Ada']); // "Hello Ada!" StringHelper::formatTags('Hi [name]', ['name' => 'Ada'], fn ($tag) => "[{$tag}]"); // "Hi Ada" StringHelper::initials('John Doe'); // "JD" StringHelper::initials('Ada Lovelace Byron', 3); // "ALB" StringHelper::toDottedClassName('App\Models\UserAccount'); // "app.models.user-account" StringHelper::fromDottedClassName('app.models.user-account'); // "App\Models\UserAccount" StringHelper::autoEncodeToUtf8($legacyString); // detects the current encoding and converts to UTF-8 StringHelper::decodeLegacyText($latin1String); // collapses whitespace, strips , decodes Latin-1 (deprecated: only for known non-UTF-8 input)
Need to replace only the first occurrence of a substring, or slugify a string for a URL? Use Laravel's own
Str::replaceFirst()/Str::slug()directly — this package used to wrap both, but they're already exactly what you need with no difference in behavior.
ArrayHelper
CaueSantos\Support\Helpers\ArrayHelper — recursive array transforms,
plucking, key renaming, and tree building.
use CaueSantos\Support\Helpers\ArrayHelper; ArrayHelper::unsetRecursive( ['a' => 1, 'b' => ['c' => 2, 'd' => 'remove-me']], ['remove-me'] ); // ['a' => 1, 'b' => ['c' => 2]] ArrayHelper::removeEmpty([ 'name' => 'Ada', 'nickname' => '', 'age' => 0, 'tags' => [], ]); // ['name' => 'Ada', 'age' => 0] — 0/false are kept, blank values are stripped ArrayHelper::deepMerge( ['settings' => ['theme' => 'dark', 'tags' => ['a']]], ['settings' => ['locale' => 'en', 'tags' => ['b']]], ); // ['settings' => ['theme' => 'dark', 'tags' => ['a', 'b'], 'locale' => 'en']] ArrayHelper::diffRecursive( ['name' => 'Ada', 'address' => ['city' => 'Paris', 'zip' => 'E1']], ['name' => 'Ada', 'address' => ['city' => 'London', 'zip' => 'E1']], ); // ['address' => ['city' => 'Paris']] — only the changed leaf, not the whole nested array ArrayHelper::toTree([ ['id' => 1, 'parent_id' => null, 'name' => 'Root'], ['id' => 2, 'parent_id' => 1, 'name' => 'Child'], ]); // [['id' => 1, ..., 'children' => [['id' => 2, ..., 'children' => []]]]] ArrayHelper::valuesRecursive(['a' => 1, 'b' => ['a' => 2, 'c' => 3]], 'a'); // [1, 2] ArrayHelper::pluckRecursive(['id' => 1, 'child' => ['id' => 2]], 'id'); // [1, 2] ArrayHelper::renameKeysNested( [['old' => 1, 'keep' => 'x']], ['old' => 'new'] ); // [['new' => 1, 'keep' => 'x']] ArrayHelper::convertKeysToCamel(['foo_bar' => 1, 'baz_qux' => 2]); // ['fooBar' => 1, 'bazQux' => 2] ArrayHelper::isListOfArrays([['id' => 1], ['id' => 2]]); // true — a sequential list containing at least one array ArrayHelper::isListOfArrays(['id' => 1]); // false // Normalize request-style scalars: "true"/"false"/"123" -> real bool/int ArrayHelper::normalizeScalarsRecursive(['active' => 'true', 'count' => '5']); // ['active' => true, 'count' => 5] // Join several attributes of ONE related record with " - " ArrayHelper::joinRelationAttributes( ['user' => ['first_name' => 'Ada', 'last_name' => 'Lovelace']], ['user', 'first_name@last_name'] ); // "Ada - Lovelace"
isListOfArrays()looks similar to Laravel's nativeArr::isList()but checks something different:Arr::isList()only cares whether the keys are sequential, whileisListOfArrays()additionally requires at least one element to itself be an array.
modifyRecursive(), mapRecursive(), buildPathArrays() and
pluckGrouped() cover tree/menu-shaped data — see their docblocks in
src/Helpers/ArrayHelper.php for the exact shape each one expects and
returns (buildPathArrays() in particular returns one flattened list of
every leaf's path segments, not one path-array per leaf — check its
docblock before relying on the shape).
ObjectHelper
CaueSantos\Support\Helpers\ObjectHelper — deep array ⇄ stdClass
conversion via a JSON round-trip.
use CaueSantos\Support\Helpers\ObjectHelper; ObjectHelper::toArray((object) ['a' => 1, 'b' => (object) ['c' => 2]]); // ['a' => 1, 'b' => ['c' => 2]] ObjectHelper::toStdClass(['a' => 1, 'b' => ['c' => 2]]); // stdClass { a: 1, b: stdClass { c: 2 } }
DateHelper
CaueSantos\Support\Helpers\DateHelper — date validation/formatting and
minute-to-hour formatting.
use CaueSantos\Support\Helpers\DateHelper; DateHelper::isValidDate('2024-01-31', 'Y-m-d'); // true DateHelper::isValidDate('2024-02-30', 'Y-m-d'); // false (not a real date) DateHelper::formatUkDate('2024-03-05'); // "05/03/2024" DateHelper::formatUkDate('2024-03-05 14:30:00', true); // "05/03/2024 14:30h" DateHelper::minutesToHours(90); // "1:30" DateHelper::minutesToHours(65); // "1:05" DateHelper::minutesToHours(120); // "2"
isValidDate()isn't the same as Carbon's ownCarbon::hasFormat():hasFormat()only checks that a string matches a format's shape — it actually returnstrueforCarbon::hasFormat('2024-02-30', 'Y-m-d'), an impossible date.isValidDate()round-trips the value back through the same format and rejects it if it doesn't match exactly, so it correctly returnsfalsethere.For "now, in a given timezone", just use Laravel's own
now('UTC')— no helper needed.
NumberHelper
CaueSantos\Support\Helpers\NumberHelper — byte formatting, memory usage,
lightweight id generation, and clamping.
use CaueSantos\Support\Helpers\NumberHelper; NumberHelper::formatBytes(1536); // "1.5 KB" NumberHelper::formatBytes(1024 * 1024); // "1 MB" NumberHelper::clamp(15, 0, 10); // 10 — constrain a value between a min and max NumberHelper::memoryUsage(); // e.g. "2.34 megabytes" — current PHP memory usage // A low-stakes, human-facing id — NOT guaranteed unique, don't use for // primary keys or anything security-sensitive. NumberHelper::almostUniqueId();
Laravel's own
Number::fileSize()(Illuminate\Support\Number) looks similar but always pads to the given precision (Number::fileSize(1536, precision: 2)->"1.50 KB"), whereformatBytes()trims trailing zeros ("1.5 KB"). Pick whichever output style you want — they're genuinely different, not interchangeable.
MoneyHelper
CaueSantos\Support\Helpers\MoneyHelper — rounding-safe, cents-based money
conversion and arithmetic. For currency formatting (symbols, locales), use
Laravel's own Number::currency() instead — this class only covers what
that doesn't: safe integer arithmetic.
use CaueSantos\Support\Helpers\MoneyHelper; MoneyHelper::toCents(19.99); // 1999 MoneyHelper::fromCents(1999); // 19.99 MoneyHelper::formatFromCents(1999); // "19.99" // Split $10.00 three ways without losing or gaining a cent to float rounding MoneyHelper::allocate(1000, 3); // [334, 333, 333] — sums back to exactly 1000
ValueHelper
CaueSantos\Support\Helpers\ValueHelper — emptiness checks and
instance-of checks that read better than nested ternaries.
use CaueSantos\Support\Helpers\ValueHelper; ValueHelper::firstValue(null, '', 'fallback', 'unused'); // "fallback" ValueHelper::firstValue(null, '', 0, 'unused'); // 0 — Laravel's filled() treats 0 as filled ValueHelper::valueOrNull(''); // null ValueHelper::valueOrNull('x'); // "x" ValueHelper::emptyMultiple(null, '', 0, []); // true ValueHelper::emptyMultiple(null, 'x'); // false ValueHelper::isInstanceOfAny($object, [Countable::class, Traversable::class]); ValueHelper::isInstanceOfAll($object, [Countable::class, Traversable::class]);
ValidationHelper
CaueSantos\Support\Helpers\ValidationHelper — the one thing Laravel
doesn't already give you a one-liner for: namespacing attributes for
"each row of this array" validation rules.
use CaueSantos\Support\Helpers\ValidationHelper; ValidationHelper::toRowValidationAttributes(['name' => 'Name', 'email' => 'Email'], 'rows'); // ['rows.*.name' => 'Name', 'rows.*.email' => 'Email']
To build a
ValidationExceptionfrom a plainattribute => message(s)array, just call Laravel's own\Illuminate\Validation\ValidationException::withMessages($errors)directly — it already accepts exactly that shape, no wrapper needed.
HtmlHelper
CaueSantos\Support\Helpers\HtmlHelper — small HTML-generation utilities.
use CaueSantos\Support\Helpers\HtmlHelper; // Safe to drop straight into a Blade attribute: <div data-payload="{{ ... }}"> HtmlHelper::encodeForHtmlAttribute(['id' => 1, 'name' => 'Ada']); // Array -> HTML attribute string, for contexts outside a Blade component's // attribute bag (mailables, PDFs, plain-PHP string building) HtmlHelper::attributes(['class' => 'btn', 'disabled' => true, 'title' => null]); // 'class="btn" disabled' — true renders bare, false/null are omitted // A quick Bootstrap-styled <table> from an array/Collection of rows HtmlHelper::simpleTable( [['name' => 'Bob', 'age' => 30], ['name' => 'Sue', 'age' => 25]], [ 'name', // dotted attribute path 'age', 'label' => fn ($value, $row) => strtoupper($row['name']), // computed column ] );
FileHelper
CaueSantos\Support\Helpers\FileHelper — temp files, zip/unzip, CSV
generation, MIME labels.
use CaueSantos\Support\Helpers\FileHelper; // Written now, deleted automatically at the end of the request $path = FileHelper::temporaryFile('report.csv', $csvContents); FileHelper::zip(storage_path('archive.zip'), ['report.csv' => $path]); FileHelper::unzip(storage_path('extracted'), storage_path('archive.zip')); FileHelper::generateCsvString([['name', 'age'], ['Ada', 36]]); // "name,age\nAda,36\n" FileHelper::mimeLabel('image/png'); // "PNG Image" FileHelper::mimeLabel('application/x-foo'); // "Unknown File" (default fallback)
UrlHelper
CaueSantos\Support\Helpers\UrlHelper — external-vs-internal URL detection
and open-redirect protection. Laravel doesn't ship anything for this out of
the box.
use CaueSantos\Support\Helpers\UrlHelper; UrlHelper::isExternal('/dashboard'); // false — relative UrlHelper::isExternal('https://example.com/dashboard'); // false — same host as config('app.url') UrlHelper::isExternal('https://evil.com/dashboard'); // true // Guard a `?redirect=` query param before redirecting a user to it UrlHelper::isSafeRedirectTarget($request->query('redirect')); // true only for a relative path or your own host UrlHelper::isSafeRedirectTarget('//evil.com/path'); // false — protocol-relative bypass UrlHelper::isSafeRedirectTarget('https://partner.com', ['partner.com']); // true — explicit allow-list
EnumHelper
CaueSantos\Support\Helpers\EnumHelper — utilities for any PHP 8.1+ enum
(backed or pure).
use CaueSantos\Support\Helpers\EnumHelper; EnumHelper::values(Status::class); // ['open', 'closed'] (backing values), or case names for a pure enum EnumHelper::names(Status::class); // ['Open', 'Closed'] // value => label pairs for a <select>, defaulting to a "Title Case" label EnumHelper::toSelectOptions(Status::class); // ['open' => 'Open', 'closed' => 'Closed'] EnumHelper::toSelectOptions(Status::class, ['Open' => 'Currently open']); // ['open' => 'Currently open', 'closed' => 'Closed']
See also InteractsWithEnum to add these as methods
directly on the enum itself.
CollectionHelper
CaueSantos\Support\Helpers\CollectionHelper — building nested Laravel
Collections that support both array and object-style access.
use CaueSantos\Support\Helpers\CollectionHelper; $data = CollectionHelper::toDeepCollection([ 'name' => 'Ada', 'address' => ['city' => 'London', 'zip' => 'E1'], ]); $data->name; // "Ada" $data['name']; // "Ada" — same value, array access $data->address->city; // "London" — nested arrays become nested Collections too $data->toArray(); // ['name' => 'Ada', 'address' => ['city' => 'London', 'zip' => 'E1']]
EloquentHelper
CaueSantos\Support\Helpers\EloquentHelper — relation-loaded checks, model
mapping, and closures for Eloquent's constrained eager loading.
use CaueSantos\Support\Helpers\EloquentHelper; // Check a relation (or a dot-path of relations) is already loaded, without // triggering a lazy load — safe to call speculatively. EloquentHelper::modelHasRelations($post, 'author'); EloquentHelper::modelHasRelations($author, 'posts.comments'); // Build a fresh, unsaved model from a plain array $mapper = EloquentHelper::mapToModel(Post::class); $post = $mapper(['title' => 'Hello world']); // The withXxx() methods return closures for constrained eager loading: // `Model::with(['relation' => EloquentHelper::withXxx(...)])`. Each one // normalizes a scalar-or-array-or-null input the way the underlying // Eloquent method itself doesn't — for a plain where()/orderBy()/ // withTrashed() constraint, just write `fn ($q) => $q->where(...)` // yourself, it's exactly as short. Post::with(['author' => EloquentHelper::withoutGlobalScopes()])->get(); // removes every global scope Post::with(['author' => EloquentHelper::withOnly(['name', 'email'])])->get(); Post::with(['comments' => EloquentHelper::without(['replies'])])->get(); Post::with(['comments' => EloquentHelper::withScope(['published', 'recent' => [30]])])->get();
SchemaHelper
CaueSantos\Support\Helpers\SchemaHelper — schema introspection, DDL
helpers and SQL fragment building.
use CaueSantos\Support\Helpers\SchemaHelper; SchemaHelper::wrapColumn('name', 'users'); // "`users`.`name`" (grammar-specific quoting) SchemaHelper::unwrapColumn('`users`.`name`'); // "users.name" SchemaHelper::buildAggregateExpression('amount', 'sum', 'total'); // "SUM(amount) as total" SchemaHelper::buildAggregateExpression('id'); // "COUNT(id)" SchemaHelper::shortForeignKeyName('a_very_long_table_name', 'a_very_long_column'); // "fk_" + 24-char hash, always <= 64 chars SchemaHelper::dropColumnIfExists('posts', 'legacy_column'); SchemaHelper::dropForeignKeyIfExists('posts', 'author_id'); SchemaHelper::dropForeignKeyAndColumnIfExists('posts', 'author_id');
dropColumnIfExists() is portable across every driver Laravel supports
(MySQL, MariaDB, Postgres, SQLite, SQL Server). The foreign-key methods are
portable on Laravel 11+ (via Schema::getForeignKeys()), with a
MySQL/MariaDB-only fallback on older versions — note that SQLite's foreign
keys have no constraint name at all, so they can be detected but not
dropped there. canBeHistorical() stays MariaDB-only: system versioning
has no equivalent in MySQL, Postgres or SQL Server.
SqlHelper
CaueSantos\Support\Helpers\SqlHelper — parsing and analyzing raw SQL
strings. Requires greenlion/php-sql-parser (see
Optional dependencies).
use CaueSantos\Support\Helpers\SqlHelper; SqlHelper::getExpressionByAlias('SELECT COUNT(*) as total FROM posts', 'total'); // "COUNT(*)" SqlHelper::containsMySQLFunction('CONCAT(first_name, last_name)'); // true SqlHelper::containsMathOperators('price * 1.2'); // true SqlHelper::hasAggregation($query); // true if the query (string or Builder) contains SUM/COUNT/AVG/...
LaravelHelper
CaueSantos\Support\Helpers\LaravelHelper — small, one-off framework
integrations that don't warrant their own class.
use CaueSantos\Support\Helpers\LaravelHelper; // 409 response required by Inertia when redirecting from a PUT/PATCH/DELETE // request. Requires inertiajs/inertia-laravel — see Optional dependencies. return LaravelHelper::forceInertiaRedirect(route('posts.index')); // Log a user in from a queue job without leaking a stale user between jobs // on a long-running worker (Horizon). LaravelHelper::queueSafeLogin($job->user_id); // Every translation file for a locale, flattened into one array keyed by filename LaravelHelper::loadLocaleTranslations('en');
CacheHelper
CaueSantos\Support\Helpers\CacheHelper — a cache-stampede-safe
remember(). Requires a cache store that supports atomic locks (redis,
memcached, database, dynamodb, or the array store).
use CaueSantos\Support\Helpers\CacheHelper; // If the key is missing, only one process computes the callback while every // other concurrent caller waits on a lock and then reads the fresh value — // unlike Cache::remember(), where every concurrent miss runs the callback. CacheHelper::rememberSafely('expensive-report', now()->addHour(), function () { return Report::buildExpensiveReport(); });
Looking for a "serve stale data while refreshing in the background" strategy instead of blocking? That's Laravel's own
Cache::flexible()— a different trade-off, not a replacement for this.
LabelHelper
CaueSantos\Support\Helpers\LabelHelper — tiny display-label formatting.
use CaueSantos\Support\Helpers\LabelHelper; LabelHelper::yesNo(true); // "Yes" LabelHelper::yesNo(0); // "No" LabelHelper::boolLabel($post->is_active, 'Active', 'Inactive'); LabelHelper::statusLabel($post->status, ['draft' => 'Draft', 'published' => 'Published']); LabelHelper::statusLabel('archived', ['draft' => 'Draft'], default: 'Unknown'); // "Unknown"
SseHelper
CaueSantos\Support\Helpers\SseHelper — a minimal Server-Sent Events
response helper.
use CaueSantos\Support\Helpers\SseHelper; SseHelper::start(); // sends SSE headers and clears output buffers foreach ($progressUpdates as $update) { SseHelper::send(['percent' => $update], event: 'progress'); } SseHelper::end('done'); // sends a final "end" event and terminates the request
Object hydration
Two traits and one static class work together to fill a plain PHP object's
typed properties from an array — casting scalars to their declared type,
parsing dates into Carbon, resolving enums, building nested Collections,
and recursively hydrating nested typed objects.
use CaueSantos\Support\IsHydratable; use CaueSantos\Support\AutoJsonSerializable; use Illuminate\Support\Carbon; class Address { use IsHydratable, AutoJsonSerializable; public string $city = ''; public ?string $zip = null; } class Person { use IsHydratable, AutoJsonSerializable; public string $name = ''; public int $age = 0; public ?Carbon $joinedAt = null; public Address $address; } $person = Person::hydrate([ 'name' => 'Ada', 'age' => '36', // cast to int 'joinedAt' => '2024-01-01', // parsed into Carbon 'address' => ['city' => 'London', 'zip' => 'E1'], // recursively hydrated ]); $person->age; // 36 (int) $person->joinedAt->isToday(); // false — it's a real Carbon instance $person->address->city; // "London"
IsHydratable—usethis on any class to get::hydrate($data)(new instance),::hydrateNoConstructor($data)(skips your__construct(), useful when it has required arguments), and->hydrateCurrent($data)(hydrate an already-built instance in place). Accepts an array, anArrayAccessimplementation, or astdClass(e.g. straight fromjson_decode()).AutoJsonSerializable— addsjsonSerialize(): array, reflecting over every non-static public/protected/private property (recursing into nested objects, unwrapping enum values). To havejson_encode($person)call it automatically, alsoimplements \JsonSerializableon the class; otherwise call$person->jsonSerialize()directly. OverrideserializeWith()to merge in computed keys that aren't backed by a real property.ClassHydrator— the static engine both traits delegate to.ClassHydrator::hydrate($existingInstance, $data)— hydrate an object you didn't build throughIsHydratable.ClassHydrator::hydrateMany(Person::class, $rows)— hydrate a list of arrays into aCollectionofPersoninstances in one call.ClassHydrator::dehydrate($person)— the reverse of hydration: a plain array back out of a hydrated object (enums unwrapped to their value, dates formatted as ISO-8601 strings, nested objects/Collections recursed into), without going through a JSON round-trip likeAutoJsonSerializabledoes.
ClassPropertyHelper— the lower-level type-coercion primitives (toBoolean(),toInteger(),convertValue(), ...)ClassHydratoris built on. Most consumers won't need to call it directly.
Model traits (HasSlug, HasMetadata)
HasSlug auto-generates a slug column from another attribute when a
model is created, appending -2, -3, etc. if it would otherwise collide:
use CaueSantos\Support\HasSlug; class Post extends Model { use HasSlug; // Optional overrides — these are the defaults: public function slugSourceField(): string { return 'name'; } public function slugField(): string { return 'slug'; } public function shouldUpdateSlugOnUpdate(): bool { return false; } } Post::create(['name' => 'My Cool Title'])->slug; // "my-cool-title"
HasMetadata gives a model a flexible, dot-notation-aware key-value
store backed by a single JSON column (default: meta — cast it to array):
use CaueSantos\Support\HasMetadata; class Post extends Model { use HasMetadata; protected $casts = ['meta' => 'array']; } $post->setMeta('seo.title', 'Hello, world'); $post->getMeta('seo.title'); // "Hello, world" $post->getMeta('seo.missing', 'x'); // "x" — default when missing $post->hasMeta('seo.title'); // true $post->forgetMeta('seo.title');
InteractsWithEnum
use on a backed or pure enum to add a default label() derived from the
case name, plus options()/values()/names() static passthroughs to
EnumHelper. Override label() on the enum itself for
custom per-case labels.
use CaueSantos\Support\InteractsWithEnum; enum Status: string { use InteractsWithEnum; case Open = 'open'; case Closed = 'closed'; } Status::Open->label(); // "Open" (Str::headline() of the case name) Status::options(); // ['open' => 'Open', 'closed' => 'Closed']
Makeable objects (IsMakeable, Component)
IsMakeable is a one-method trait: use it on any class to get a
::make(...$arguments) static constructor that just forwards to new static(...$arguments) — handy for a fluent, static-friendly construction
style.
use CaueSantos\Support\IsMakeable; class Money { use IsMakeable; public function __construct(public int $cents, public string $currency = 'USD') {} } Money::make(500, 'GBP'); // same as `new Money(500, 'GBP')`
Component is a tiny, ready-made value object built on IsMakeable for
describing a named UI component with its data — e.g. handing a Blade/Inertia
component name and props around as a single value:
use CaueSantos\Support\Component; $component = Component::make('UserCard', ['id' => 1]); $component->name; // "UserCard" $component->data; // ['id' => 1]
Runtime migration generation
RuntimeMigration builds a real Laravel migration file (not a
migration run in-process) from a Blueprint closure, so you can generate
migrations programmatically — e.g. from a schema-builder UI, an artisan
command, or a code generator.
use CaueSantos\Support\RuntimeMigration; use Illuminate\Database\Schema\Blueprint; $migration = RuntimeMigration::make('posts', function (Blueprint $table) { $table->string('title'); $table->text('body')->nullable(); }); $path = $migration->create(database_path('migrations')); // writes a real timestamped `..._create_posts_table.php` migration file and // returns its path $migration ->afterMigrationCreated(fn (string $file, string $table) => Log::info("Generated {$file} for {$table}")) ->create(database_path('migrations')); // Generate an "update" migration for an existing table instead — its down() // reverses the same add-column statements (MySQL/MariaDB syntax). $migration->update(database_path('migrations'));
If the target directory doesn't exist, or the file can't be written,
create()/update() throw CaueSantos\Support\Exceptions\MigrationGenerationException.
Two smaller pieces support this:
Stub— a minimal{{ placeholder }}template renderer used to fill in the bundledstubs/migration.create.stubandstubs/migration.update.stubtemplates.Stub::create($path, ['table' => 'posts'])->render().HistoricalDataMigration— wraps MariaDB's system-versioning (ADD SYSTEM VERSIONING) DDL so an "update" migration generated byRuntimeMigrationcan safely alter a historical/versioned table. MariaDB only.
Optional dependencies
Two methods need a package this library doesn't force on every consumer —
both are declared under Composer's suggest, not require, so install them
only if you actually call the method that needs them:
| Package | Needed for |
|---|---|
greenlion/php-sql-parser |
SqlHelper::getExpressionByAlias() |
inertiajs/inertia-laravel |
LaravelHelper::forceInertiaRedirect() |
Testing & code quality
composer install vendor/bin/phpunit # test suite vendor/bin/pint --test # code style (Laravel preset) vendor/bin/phpstan analyse # static analysis (Larastan, level 5)
All three run in CI on every push and pull request, across a PHP 8.2–8.4
matrix (see .github/workflows/).
Security
If you discover any security related issues, please email instead of using the issue tracker.
License
MIT. See LICENSE.