Skip to content
DedicatedPHP Contact

Capabilities and limits in a PHP SaaS without plan conditionals

Design auditable capabilities, limits, and exceptions in a PHP SaaS so commercial plans do not end up scattered across conditionals.

Editorial diagram of capabilities, limits, permissions, and centralized rules for a multi-tenant PHP SaaS platform

When SaaS logic starts with conditions such as if ($tenant->plan === 'pro'), it seems like a straightforward solution. The problem emerges with the second catalog change: a plan is renamed, a capability is sold separately, a customer retains legacy terms, or support needs to enable something temporarily. The commercial name then stops describing a stable rule and ends up scattered across controllers, scheduled tasks, queries, APIs, and the interface.

Capability management in PHP SaaS must translate the commercial offering into verifiable domain decisions. The code should not ask whether a company is “Pro,” but whether it can perform a specific action, with what limit, under what conditions, and until when. This separation makes it possible to change pricing or packaging without rewriting operational rules.

Separate plan, capability, limit, permission, and configuration

Separate plan, capability, limit, permission, and configuration — DedicatedPHP visual guide

These concepts are related, but they are not interchangeable. A plan is commercial packaging. A capability enables a product possibility, such as exporting data, creating automations, or using an integration. A limit defines an allowed quantity or rate, for example active projects, billable users, or API requests per period.

A permission answers a different question: which identity can perform an action within a company. An organization having the capability to export data does not mean that any user can export it. Finally, customer configuration consists of options valid for a specific company, such as the selected identity provider, a retention policy, or a notification template. Free-form configuration should not be used to hide commercial or authorization decisions.

  • Plan: commercial composition of entitlements and limits.
  • Capability: functional rule expressed in product terms.
  • Limit: quantifiable threshold associated with a capability or resource.
  • Permission: authorization for an actor to perform an action.
  • Configuration: behavior parameter available within the rules already granted.

A decision may require all layers. To create an automation, the company needs the corresponding capability, must be below the active automations limit, and the user must have administrative permission. The flow can then validate the destination configuration.

Model domain rules, not plan names

Maintain a stable capability catalog with technical identifiers independent of marketing: automation.create, data.export, or api.webhooks. The catalog can include the expected value type: boolean, integer, set of options, or structured policy. The identifier expresses a product need; it must not incorporate a plan name or campaign.

The commercial assignment can be resolved in a separate layer. A current plan provides a set of grants, but add-ons, legacy migrations, and explicit exceptions may also exist. The result for each company is an effective entitlement resolution with provenance.

capability: automation.create
 effective value: true
 provenance: automation add-on
 validity: until cancellation

This provenance is essential. If a capability is active, product, support, and billing need to know whether it comes from the current plan, a legacy clause, or an exception with an expiration date. Avoid storing only a plan field on the company and inferring everything else at every point of use.

A single decision service

In PHP, expose a domain service, for example EntitlementResolver or CapabilityGate, that receives the company, the capability, and the required context. It should return an explainable decision, not just a boolean: allowed or denied, resolved value, reason, source rule, and evaluation date. This date represents the instant at which the resolver determined the decision and makes it possible to correctly interpret validity periods, expirations, and subsequent plan changes. Controllers, commands, listeners, and asynchronous workers query that service; they do not rebuild their own subscription queries.

Define measurable limits before implementing them

An ambiguous limit creates conflicts and implementation errors. “Up to 100 users” requires answering what counts as a user: a pending invite, a suspended user, a member deleted during the cycle, a service account? “One thousand exports” requires defining the period, time zone, retries, and whether a failed export consumes quota.

For each limit, document at least:

  • The resource, event, or consumption being counted.
  • The scope: company, project, user, or integration.
  • The window: current total, calendar day, billing month, or rolling window.
  • The enforcement point: before creation, on activation, on sending, or after consolidation.
  • The response: block, allow with a warning, queue, degrade, or require approval.
  • The handling of concurrency, retries, cancellations, and rollback.

Active object limits are usually validated with a transactional operation or a reservation that prevents exceeding the threshold under concurrency. Consumption limits need a counter with clear semantics and idempotency through an event key. Do not rely only on a counter displayed in the interface: two simultaneous requests can pass a prior check and exceed the maximum.

Also distinguish between warning and blocking. An alert at 80% improves predictability, but it does not replace real protection at the point where the resource is created or executed.

Apply rules across all execution paths

Hiding a button is an experience improvement, not an access control. Validation must exist in the server-side use case that executes the action. This covers the web interface, public API, integrations, and internal calls.

Asynchronous processes require an additional decision: check when queuing and check again when executing if the job may be delayed. If a company loses a capability between those two points, the policy must define whether the job is canceled, completed because it was accepted earlier, or requires review. The choice depends on the operation type, but it must be consistent and recorded.

Support and administration tools should not silently bypass rules. They can operate with separate administrative authorization, but they must leave an audit trail and indicate whether they create a formal grant, a data correction, or an exceptional action.

Manage changes, legacy entitlements, and exceptions without branching the product

A plan change is not just updating a label. It may reduce a limit below current usage or remove a capability that supports active processes. Define policies by resource type: prevent new creations and preserve existing ones, explicitly deactivate excess items, provide a transition period, or request a choice from the company administrator.

Temporary exceptions must be first-class grants with scope, value, reason, issuer, and expiration. A manual field such as is_vip is difficult to interpret and often outlives its original cause. For legacy customers, model a migration assignment with precise rules and a review date instead of creating permanent code branches.

A sustainable exception is auditable data interpreted by the same resolver; a dangerous exception is a special conditional added to a specific flow.

Combine capabilities, roles, and multi-tenant isolation

In a multi-tenant platform, every entitlement, consumption, and configuration query must be scoped to the correct company. Do not derive context solely from values sent by the client. Resolve it from authentication, the requested domain, or validated internal context, and propagate it to queued jobs and events.

The final decision is usually an intersection: the company has the capability, the limit has not been exhausted, and the actor has the required permission. Centralizing capabilities does not replace a role model; it prevents roles and plans from becoming mixed. A role can grant who administers automations, while the capability determines whether the company can use automations.

Data, auditing, and tests that make decisions explainable

Keep entitlement assignments with validity periods and precedence, together with consumption events when an aggregate is not enough. Record relevant decisions: company, actor or process, capability, evaluated value, result, source, and request correlation. Do not store unnecessary personal data in these records, and establish retention in accordance with your obligations.

The audit trail must explain why an action was denied without requiring anyone to read historical code. It is especially useful for commercial changes, billing incidents, and support operations.

Tests must include a matrix of capabilities and values, limits at the exact boundary, concurrency, plan changes, exception expiration, and event retries. Run the same cases through HTTP, API, and queue workers when they share a use case. Add isolation tests to confirm that one company cannot query or consume another company's entitlements.

Gradual plan for centralizing an existing PHP platform

  1. Inventory plan names, conditions, counters, and manual exceptions in code and operations.
  2. Choose a high-impact capability or limit and define its complete semantics before migrating it.
  3. Introduce the resolver as a facade, initially compatible with the current sources.
  4. Move enforcement points to the central use case, not only to the interface.
  5. Record decisions and compare new behavior with previous behavior before removing old branches.
  6. Migrate plan by plan to explicit assignments and remove commercial references from the domain.

Checklist before publishing a change

Checklist before publishing a change — DedicatedPHP visual guide
  • Does the capability have a stable identifier and an unambiguous business definition?
  • Does the limit specify the unit, scope, period, concurrency, and response to exceeding it?
  • Is the rule enforced in the server, API, and asynchronous processes?
  • Are roles, capabilities, and company context validated separately?
  • Do plan changes and exceptions have validity, provenance, and auditing?
  • Are there tests for the limit boundary, revocation, and multi-tenant isolation?

With this model, the commercial catalog can evolve without turning every change into a search for conditionals. The platform retains understandable, measurable, and defensible rules for both product and engineering.

Want to apply these ideas to your project?Let’s discuss your PHP platform.
View related service