tourze/user-level-bundle
A Symfony bundle for managing user levels and ranks, providing configurable level definitions and easy integration for tiered membership, permissions, or gamification features in your application.
tourze/user-level-bundle is a pre-1.0 package for modelling user levels/grades (tiers, ranks, progression) as first-class domain objects rather than a loose integer column. Version 0.1.0 is the first minor bump off 0.0.2, and the only release note is a compare/0.0.2...0.1.0 link — there is no changelog prose, so read the diff before upgrading rather than trusting the version number.
Minimal path in:
composer require tourze/user-level-bundle:^0.1 — pin to ^0.1, never *, on a 0.x line.https://github.com/tourze/user-level-bundle/compare/0.0.2...0.1.0. On a 0.x package a minor bump is where breaking changes legally live, so look specifically for renamed classes, changed constructor signatures, and removed interfaces.vendor/tourze/user-level-bundle/src and list the public surface:
find vendor/tourze/user-level-bundle/src -name '*.php' | sort
grep -rn "interface \|abstract class " vendor/tourze/user-level-bundle/src
*Bundle.php) and any Resources/config/ or config/ directory. Tourze packages are conventionally Symfony-flavoured (Doctrine entities, DI service definitions, *Interface contracts) even when consumed from Laravel — expect contracts and entities, not a Laravel service provider.users table/model and resolve a user's current level through the package's service rather than computing it inline. That single read path is the cheapest way to prove the package fits before wiring promotion logic.If src/ turns out to expose only entities with no Laravel binding, budget a small bridge (see below) before committing.
Treat the package as a domain layer, not a Laravel package. The realistic integration is: use its entities/value objects and level-resolution services, and own the framework glue yourself.
Bridge service provider. If no UserLevelServiceProvider is auto-discovered, register the package's services manually and bind its contracts:
// app/Providers/UserLevelServiceProvider.php
public function register(): void
{
$this->app->singleton(LevelResolverInterface::class, fn ($app) =>
new CachedLevelResolver(
$app->make(BaseLevelResolver::class),
$app->make('cache.store'),
)
);
}
Keep the level off your users table as a source of truth. Store experience/points (or the inputs the package expects) and derive level via the resolver. Denormalise a level_id column only as a read optimisation, refreshed on the same transaction that changes the inputs — never as the only copy.
Expose it through an accessor so views stay dumb:
protected $appends = ['level'];
public function level(): Attribute
{
return Attribute::get(fn () => app(LevelResolverInterface::class)->for($this));
}
Promotion as a domain event. Whatever the package offers for "does this user qualify for the next level", call it from one place — a LevelService or a queued listener on your own ExperienceGained event — and fire a UserLevelledUp event from there. Don't scatter threshold checks across controllers; the package's rules will change under you on a 0.x line and you want one seam to patch.
Cache aggressively. Level lookups are read-heavy and change rarely per user. Cache the resolved level keyed by user id and forget it in the same place you mutate experience. A CachedLevelResolver decorator (as above) keeps the package's classes untouched.
Testing. Bind a fake resolver in tests instead of seeding real level rows:
$this->app->bind(LevelResolverInterface::class, fn () => new FixedLevelResolver(level: 7));
Assert on derived level, not on stored columns, so a change in the package's threshold maths doesn't silently invalidate your suite's meaning.
0.1.0 from 0.0.2 can rename or delete anything. Lock the exact version in composer.json ("0.1.0", not ^0.1) in production until you've read a diff end to end, and upgrade deliberately.composer update tourze/user-level-bundle --dry-run -v and diff vendor/ against your VCS copy before deploying. Treat "no changelog" as "unknown breaking change".EntityManager-not-found errors here.extra.laravel.providers block. Check composer.json in vendor/tourze/user-level-bundle for extra.laravel before assuming php artisan vendor:publish will work — it almost certainly won't.config.yaml/parameters, mirror the values you care about into config/user-level.php and pass them in explicitly at construction; don't depend on the package finding a file at a path Laravel doesn't use.null unexpectedly, check the resolver's boundary conditions first (off-by-one at exact thresholds, and users with zero experience). Then check the cache — a stale entry after an experience mutation looks identical to a logic bug.src/ defines *Interface contracts, bind your own implementations and let the package be the default. That keeps a future 0.2.0 breaking change confined to one binding line.tourze/user-level-bundle so you see commits land rather than discovering them at composer update time.How can I help you explore Laravel packages today?