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

Easy License Laravel Package

armincms/easy-license

A Laravel package for adding license management to your PHP applications and packages. Generate, validate, and manage license keys to protect and distribute commercial or private software, with a straightforward setup that fits into existing Laravel projects and helps you control who can use your co...

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and pin. The package is at 1.3.9 and last shipped in December 2021 — pin it explicitly ("armincms/easy-license": "1.3.9") rather than using ^1.3, so an upstream surprise can only arrive on your schedule.

composer require armincms/easy-license:1.3.9

2. Publish and migrate. Run the interactive publisher first — documentation is thin, so let Artisan tell you the real tag names rather than guessing:

php artisan vendor:publish          # inspect the list, then re-run with the specific tag
php artisan migrate

The migration is your best specification. Read the licenses (and any activation/domain) table columns before writing a line of code: those columns are the actual lifecycle state machine — status, expiry, activation limits, polymorphic owner — and everything else in the package is downstream of them.

3. Look here first, in this order:

  • config/easy-license.php — storage model, expiry defaults, key format, middleware alias.
  • The service provider — reveals which contracts are bound in the container, which is where your extension point lives.
  • src/ models and any Http/ controllers — the validation surface you'll be wrapping.

4. First use case: issue-then-verify. The shortest end-to-end path is a self-hosted product that phones home. On the vendor side, generate a key when an order/fulfilment event fires and persist it with product and seat metadata. On the customer install, call the validate/activate endpoint once at boot, cache the result, and gate the app with the package's middleware. Get that loop working before touching activation limits or expiry.

Implementation Patterns

Put the package behind your own boundary. This is the single most important pattern given the package's age and size. Define an LicenseService interface (issue(), activate(), validate(), revoke()) and implement it with the package. Controllers, jobs, and middleware talk to your interface. If the package is abandoned or you outgrow it, the swap touches one class.

Issuance tied to a domain event. Don't call key generation from a controller. Listen to your fulfilment/payment event, then issue inside the same transaction or a queued job — and make the job idempotent (look up an existing license for the order first). Key generation in the checkout path is exactly where a silent failure becomes a support ticket.

Activation as an explicit record. Treat "activate" as binding a key to a concrete instance — domain, server fingerprint, install UUID — and persisting that binding. Validation should then be a cheap lookup of the binding, not a re-derivation. Make reactivation idempotent: the same instance checking in twice must not consume two seats.

Gate routes with middleware, but return the right status. Register the package's middleware alias on your licensed route group, then verify it returns 403 with a machine-readable body rather than a redirect — API clients can't follow a redirect to a login page meaningfully.

Route::middleware(['web', 'license'])->group(function () {
    // protected application routes
});

Cache the check-in, don't hit the database per request. A middleware that queries a license table on every request is a latency and availability problem. Validate once per interval (hourly/daily), cache the verdict with a TTL, and include a grace window so a transient outage of the license server doesn't black out paying customers.

Scheduled expiry sweep. Put a scheduled command in place that transitions expired licenses to an inactive state and fires a notification before expiry. This gives you the renewal touchpoint and keeps validation queries simple (status flag, not a date comparison scattered through the codebase).

Fake it in tests. Bind a stub implementation of your LicenseService (or swap the container binding) so the test suite never generates real keys or hits the network. Every test that touches licensed behaviour should be able to assert "licensed" or "unlicensed" without a database row.

Gotchas and Tips

Treat it as lightly maintained code you are adopting, not a framework you're consuming. One star, one release in recent years, and a thin README means the source is the documentation. Budget an afternoon to read src/ end to end before you build on it. If the reading turns up no tests around key generation, assume that surface is unverified and write your own coverage against it.

Note the documentation mismatch. The repository's product assessment talks about saving orders, not license keys. That suggests a template or copy-paste artefact in the repo's own docs. It's a small thing but a useful signal: don't trust the prose, trust the code and the migrations.

Check how keys are generated. Verify the implementation uses a CSPRNG (random_bytes/Str::random with sufficient length), not uniqid() or a timestamp-derived value. If it's weak, generate the key material yourself and use the package only for storage and lifecycle.

Validate on write, not just on read. Ensure uniqueness is enforced at the database level (unique index on the key column) — application-level "check then insert" will race under concurrency and hand two customers the same key.

Watch config caching. php artisan config:cache in production will bake in whatever easy-license.php contains. Anything you expect to change per environment (server URL, grace period) must come from env() at deploy time, and you must re-cache after changing .env. Debugging that starts with "it worked locally" usually ends here.

Middleware ordering matters. Place it after session/auth middleware if activation binds to a user, and before anything that mutates state. An unlicensed request must be rejected before your controller writes anything.

Clock skew and expiry boundaries. Expiry comparisons across timezones bite. Decide whether expiry is end-of-day UTC or an exact timestamp, and be generous by a few hours on the client check — a customer three hours ahead of you hitting an expired license mid-afternoon is a support call.

Migration collisions. If you already have a licenses table (common in Laravel apps — Passport, Sanctum-adjacent code, or prior licensing attempts), rename via the published migration before running it, or you'll get a duplicate-table failure on a production deploy.

Extension points to lean on: the container binding for the validator/generator contract, and the published config. Override rather than fork — a fork means you own the maintenance and lose the ability to take upstream patches, which for a package this small is a real loss.

Debugging failures: log every validation rejection with the reason (unknown key, expired, seat limit reached, activation mismatch). The package likely collapses these into a boolean; the distinction between "expired" and "wrong instance" is the difference between a renewal email and a bug report, so capture it at your wrapper layer.

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.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle