coderstm/laravel-core
Core foundation package for Laravel applications by Coderstm, bundling shared helpers, base classes, traits, and common utilities. Provides reusable scaffolding so packages and apps share consistent configuration, models, and service providers.
1. Install and publish.
composer require coderstm/laravel-core
php artisan vendor:publish --provider="Coderstm\CoderstmServiceProvider"
php artisan install
php artisan install is the package's own bootstrap (seeds payment methods, permission modules, default settings). Run it after your users/admins migrations exist, and re-read its source before running it twice — it is the single highest-risk command in the package.
2. Read these four files first, in this order.
| File | Why |
|---|---|
config/coderstm.php |
The whole feature surface: application, currency, subscription, shop, wallet, license, theme. If a behaviour surprises you, it is almost certainly toggled here. |
src/Coderstm.php |
The static facade — model binding, ignoreRoutes(), withoutCart(), ignoreMigrations(), gateway accessors. This is your contract with the package. |
CoderstmServiceProvider |
Which routes, middleware, migrations, and event listeners get registered, and under what conditions. |
src/Traits/ |
39 traits (Billable, ManagesSubscriptions, HasWallet, HasPermission, Paymentable, …). Traits, not base classes, are how you extend models. |
3. Bind your models in AppServiceProvider::boot() before touching anything else.
Coderstm::useUserModel(App\Models\User::class);
Coderstm::useAdminModel(App\Models\Admin::class);
// useSubscriptionModel, useOrderModel, etc.
4. First use case: multi-guard auth. The package ships users and admins guards on Sanctum with /auth/{guard?}/signup|login|me. Wire your SPA to those endpoints and use the helpers — user(), guard(), is_admin() — rather than auth()->user(). You get working auth for two guards in an afternoon, which is genuinely the fastest win here.
If you only need one feature (say the cart, or currency conversion), enable it and call Coderstm::ignoreRoutes() for the rest — do not adopt the whole stack on day one.
Config-driven host app, not model-extending host app. The intended shape is: the package owns routes, controllers, models, and migrations; your app owns configuration, model bindings, front-end, and a handful of override classes. If you find yourself copying package controllers into app/Http/Controllers, you have misread the design — use ignoreRoutes() and re-expose only what you need.
Feature gating is a closed loop. Define Feature → attach to Plan → gate with $user->hasFeature('slug') → meter with $user->useFeature('slug') → reset via the scheduled reset:subscriptions-usages. Never check plan_id directly; the feature layer is what the admin UI and usage counters both read.
Payment gateways: extend, don't patch.
class AcmeProcessor extends AbstractPaymentProcessor
{
public function charge(PayableInterface $payable, array $payload): PaymentResult { /* … */ }
public function refund(Payment $payment, ?float $amount = null): RefundResult { /* … */ }
public function createSubscription(...): SubscriptionResult { /* … */ }
public function cancelSubscription(...): void { /* … */ }
}
Pair every processor with a mapper in Payment/Mappers/ that normalises the provider webhook into the package's shape. Without the mapper, downstream events (RefundProcessed, SubscriptionCancelled) fire with partial data and your listeners silently misbehave.
Currency lives at the request boundary. Resolve once in middleware with Currency::resolve($address), then format everywhere else with format_currency(). Do not call Currency::set() inside controllers — the service is request-scoped and you will leak rates between requests under Octane.
Events over model overrides. Subscribe to SubscriptionCreated, SubscriptionCancelled, RefundProcessed for side effects (email, provisioning, analytics). Overriding a package model method breaks the observer chain and the queued listeners that assume it ran.
Runtime settings via ConfigLoader/AppSetting, not env(). Anything an admin can change at runtime (mail driver, payment keys, exchange rates) belongs in the DB-backed config layer. Reading env() anywhere past config/ is a bug in this architecture — and it breaks outright once you run config:cache.
Schedule the commands yourself. They are not auto-registered. In routes/console.php:
Schedule::command('subscriptions:renew')->daily();
Schedule::command('reset:subscriptions-usages')->monthly();
Schedule::command('update:exchange-rates')->daily();
Schedule::command('check:grace-period-subscriptions')->hourly();
Schedule::command('process:abandoned-checkouts')->everyFifteenMinutes();
Reports. New report = class with getData() returning a collection + registration in ReportService::$reports + optional export class. Because getData() returns raw rows, keep aggregation in SQL; 60+ report types sharing one service means an N+1 in your report is an N+1 in everyone's admin dashboard.
Route collisions are the first thing that will bite you. The package registers /auth/*, /admin/*, /user/*, /shop/*, /webhooks/*, /subscriptions/*. Any overlapping host route resolves by registration order and you will get a confusing 404/405 rather than a duplicate-route error. Audit with php artisan route:list immediately after install, and call Coderstm::ignoreRoutes() if you are not using the bundled API.
Migrations order matters. Package migrations must run after your users/admins tables, and ignoreMigrations() exists precisely for teams that need to publish and edit them. If you add columns to package tables, do it in a later migration — editing a published package migration will not re-run on existing environments.
Webhooks need three things the docs do not spell out: CSRF exemption for /webhooks/*, provider signature verification inside your controller (not the mapper), and idempotency on the Payment/Order write. Payment providers retry; a non-idempotent webhook handler here produces duplicate wallets and duplicate subscription extensions.
Config cache is a landmine. ConfigLoader reads from the DB at boot. Verify the loading order survives php artisan config:cache and optimize on your deploy — a ConfigLoader that resolves before the DB connection is available will throw on every request in production and work fine locally.
Request-scoped state in queues. Currency and ApplicationState are request-scoped. A queued job or notification dispatched from a non-INR request will format money in the caller's currency unless you snapshot the currency code into the job payload. Same trap for Blog's singleton cache — call Blog::clearCaches() in observers, and remember it persists across requests under Octane.
Permission caching. HasPermission / GuardMiddleware cache per-group permissions. After seeding new Module/Permission/Group records, clear the cache or admins will see 403s on screens that visibly exist.
Debugging path. When something behaves unexpectedly, in order: (1) config('coderstm.*') for the effective value, (2) php artisan route:list --path=… to confirm which handler actually owns the route, (3) whether the relevant event fired (log the listener, don't guess), (4) whether the artisan command that maintains the state is scheduled and succeeding — most "subscription/wallet is wrong" bugs are a command that was never scheduled.
Vendor risk is the real headline. Zero stars, zero dependents, one vendor, an enormous surface area (61 models, 39 traits, 11 payment processors), and a release date that reads as future-dated relative to normal semver cadence. That is not disqualifying for an internal or narrow-scope use, but it changes how you should consume it:
composer.lock. No ^ range on a zero-adoption package.app/, not in vendor/.Used narrowly — as a batteries-included auth + billing + cart backend behind a custom SPA — it is a large but real head start. Used as a black box across the whole money path, it is an unhedged dependency.
How can I help you explore Laravel packages today?