Global Context Management
Overview
Section titled “Overview”Toggl supports global context management similar to the Warden package, allowing you to set an additional context layer for feature evaluation. This is particularly useful for multi-tenancy scenarios where features should behave differently based on the current organizational context (team, account, workspace, etc.).
Global Context vs Entity Context
Section titled “Global Context vs Entity Context”Understanding the difference between global context and entity context is crucial:
- Entity Context: The entity being checked (e.g., a specific user, model instance)
- Global Context: The global contextual environment (e.g., which team, account, or workspace is active)
Basic Usage
Section titled “Basic Usage”Setting Global Context
Section titled “Setting Global Context”use Cline\Toggl\Toggl;
// Set context to a team IDToggl::context()->to('team-123');
// Set context to an organizationToggl::context()->to($organization);
// Clear contextToggl::context()->clear();Checking Current Context
Section titled “Checking Current Context”// Check if context is setif (Toggl::context()->hasContext()) { $context = Toggl::context()->current();}Using Context in Feature Resolvers
Section titled “Using Context in Feature Resolvers”Feature resolvers receive both the entity context and global context parameters:
Toggl::define('premium-api', function ($entityContext, $globalContext = null) { // $entityContext = the user being checked // $globalContext = the current team/organization
return $entityContext->team_id === $globalContext;});
// Set team contextToggl::context()->to(5);
// Check if user can access premium API in their team contextif (Toggl::for($user)->active('premium-api')) { // User can access premium API within team 5}Multi-Tenancy Scenarios
Section titled “Multi-Tenancy Scenarios”Team-Based Features
Section titled “Team-Based Features”// Define a feature that's only active for users within a specific teamToggl::define('advanced-analytics', function ($user, $globalContext = null) { // Feature is active if user belongs to the current team context // and the team has the premium plan return $user->team_id === $globalContext && Team::find($globalContext)?->hasPremiumPlan();});
// In your middleware or controllerToggl::context()->to($currentTeam->id);
// Check feature for userif (Toggl::active('advanced-analytics')) { // Show advanced analytics}Account-Based Features
Section titled “Account-Based Features”Toggl::define('white-label', function ($user, $globalContext = null) { if ($globalContext === null) { return false; // Not active without account context }
return Account::find($globalContext)?->hasWhiteLabel() ?? false;});
// Set account contextToggl::context()->to($request->account()->id);
// Feature will be evaluated within account contextif (Toggl::active('white-label')) { // Apply white-label branding}Workspace-Based Features
Section titled “Workspace-Based Features”Toggl::define('collaboration-tools', function ($user, $globalContext = null) { // User must be in a workspace and workspace must have collaboration enabled return $globalContext !== null && $user->workspaces->contains($globalContext) && Workspace::find($globalContext)?->hasCollaboration();});
// Set workspace context in middlewarepublic function handle($request, Closure $next){ if ($workspace = $request->route('workspace')) { Toggl::context()->to($workspace); }
return $next($request);}Context with Different Entity Types
Section titled “Context with Different Entity Types”Object Entities with Properties
Section titled “Object Entities with Properties”// Use objects for entities with multiple properties$user = (object) ['id' => 1, 'team_id' => 5];
Toggl::define('team-feature', function ($entityContext, $globalContext = null) { // $entityContext is an object with team information return isset($entityContext->team_id) && $entityContext->team_id === $globalContext;});
Toggl::context()->to(5);
$isActive = Toggl::for($user)->active('team-feature'); // trueNote: Associative arrays are treated as multiple entities by the for() method, not as a single entity with properties. Use objects or implement TogglContextable for complex entity types.
Model Entities
Section titled “Model Entities”Toggl::define('org-admin', function ($user, $globalContext = null) { if ($globalContext === null) { return false; }
return $user->isAdminOf($globalContext);});
// Using Eloquent modelsToggl::context()->to($organization);
if (Toggl::for($user)->active('org-admin')) { // User is admin in this organization context}Context as Objects
Section titled “Context as Objects”// You can use objects as context to pass structured data$teamContext = (object) ['id' => 99, 'tier' => 'enterprise'];
Toggl::define('enterprise-features', function ($user, $context = null) { return is_object($globalContext) && isset($globalContext->tier) && $globalContext->tier === 'enterprise';});
Toggl::context()->to($teamContext);
if (Toggl::active('enterprise-features')) { // Show enterprise features}Cache Behavior
Section titled “Cache Behavior”Context changes automatically flush the feature cache to ensure fresh evaluation:
Toggl::context()->to('team-123');$result1 = Toggl::active('premium-api'); // Evaluated fresh
Toggl::context()->to('team-456');// Cache is automatically flushed when context changes$result2 = Toggl::active('premium-api'); // Re-evaluated with new context
Toggl::context()->clear();// Cache is flushed when context is cleared$result3 = Toggl::active('premium-api'); // Evaluated without contextIntegration with Strategies
Section titled “Integration with Strategies”Context works seamlessly with all built-in strategies:
Conditional Strategy with Context
Section titled “Conditional Strategy with Context”use Cline\Toggl\Strategies\ConditionalStrategy;
Toggl::define('team-export', new ConditionalStrategy( fn ($user, $team = null) => $team !== null && $user->team_id === $globalContext, true, // Value when condition is true false // Value when condition is false));Custom Strategies with Context
Section titled “Custom Strategies with Context”use Cline\Toggl\Contracts\Strategy;
class TeamBasedStrategy implements Strategy{ public function __construct( private int $requiredTeamTier, ) {}
public function resolve(mixed $entityContext, mixed $globalContext = null): mixed { if ($globalContext === null) { return false; }
$team = Team::find($globalContext);
return $team && $team->tier >= $this->requiredTeamTier; }}
Toggl::define('advanced-features', new TeamBasedStrategy(requiredTeamTier: 2));Real-World Example: SaaS Application
Section titled “Real-World Example: SaaS Application”// Middleware to set account contextclass SetAccountContext{ public function handle($request, Closure $next) { if ($account = $request->user()?->currentAccount()) { Toggl::context()->to($account->id); }
return $next($request); }}
// Feature definitionsToggl::define('api-access', function ($user, $globalContext = null) { if ($globalContext === null) { return false; }
$account = Account::find($globalContext);
return $account && $account->plan->hasApiAccess();});
Toggl::define('team-collaboration', function ($user, $globalContext = null) { if ($globalContext === null) { return false; }
$account = Account::find($globalContext);
return $account && $account->plan->hasCollaboration() && $user->isTeamMember($account);});
// In your controllerpublic function index(){ // Context is already set by middleware
if (Toggl::active('api-access')) { // Show API documentation link }
if (Toggl::active('team-collaboration')) { // Show team features }}Testing with Context
Section titled “Testing with Context”use Cline\Toggl\Toggl;
test('users can access team features within their team context', function () { $user = User::factory()->create(['team_id' => 5]);
Toggl::define('team-dashboard', fn ($u, $globalContext = null) => $u->team_id === $globalContext );
// Set team context Toggl::context()->to(5);
expect(Toggl::for($user)->active('team-dashboard'))->toBeTrue();
// Change context to different team Toggl::context()->to(10);
expect(Toggl::for($user)->active('team-dashboard'))->toBeFalse();
// Clear context Toggl::context()->clear();
expect(Toggl::for($user)->active('team-dashboard'))->toBeFalse();});Best Practices
Section titled “Best Practices”- Always set context in middleware for consistent context across requests
- Check for null context in your resolvers if the feature requires context
- Use model IDs for context rather than full models for better serialization
- Clear context in tests to avoid test pollution
- Document context requirements in your feature definitions
Migration from Direct Entity Checking
Section titled “Migration from Direct Entity Checking”If you’re currently checking features like this:
// Before: mixing entity and global contextToggl::for(['user' => $user, 'team' => $team])->active('feature');Migrate to context-based approach:
// After: separate entity and global contextToggl::context()->to($team->id);Toggl::for($user)->active('feature');This provides better separation of concerns and more consistent behavior across your application.