PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
211: Tenant Identification Middleware
When you're building a SaaS application, you'll eventually hit the "multi-tenancy" problem. You have one codebase and one infrastructure, but your data is partitioned by customer. If "Bookstore A" logs in, they should never see "Bookstore B's" inventory. While you could pass a tenant_id into every single controller method, that's a recipe for a security disaster—you'll forget it once, and suddenly you've leaked private data.
The professional way to handle this is through middleware. We want to identify the tenant as early as possible in the request lifecycle and "set" that tenant globally for the duration of the request. For this example, we're going to build a system that identifies tenants based on their subdomain (e.g., books-r-us.myapp.com).
Extracting the identity from the URL
First, we need a middleware class. I'm going to assume we're using a standard PSR-15 style middleware approach. The goal here is to grab the Host header from the request, strip away our main domain, and see what's left.
public function handle(Request $request, Closure $next)
{
$host = $request->getHost(); // e.g., 'books-r-us.myapp.com'
$mainDomain = 'myapp.com';
// We split the host and remove the main domain part
$parts = explode('.', $host);
if (count($parts) > 2) {
$tenantSlug = $parts[0];
} else {
$tenantSlug = null;
}
// Now we find the tenant in the database
$tenant = Tenant::where('slug', $tenantSlug)->first();
if (!$tenant) {
abort(404, 'Tenant not found.');
}
return $next($request);
}
Binding the Tenant to the Container
Finding the tenant is one thing, but how does the rest of your app—your repositories, your models, your services—know which tenant was found? If you just leave it in the middleware, it's useless. I prefer binding the tenant instance into the service container as a singleton. This makes the tenant globally accessible but decoupled.
I'll add this line right before the return $next($request):
// Bind the actual Tenant model instance so we can resolve it anywhere
app()->instance(Tenant::class, $tenant);
Now, in any controller or service, you can just type-hint Tenant $tenant in the constructor, and PHP's container will inject the specific bookstore we identified in the middleware. It's clean, and it's testable.
The "Main Site" oversight
Here is where I usually mess up the first time I implement this. I ran the code above, navigated to my landing page at myapp.com, and—surprise—I got a 404. Why? Because my middleware is running on every request, including the marketing page where there is no subdomain.
I tried to fix this by adding a complex regex, but that's overkill. The simplest fix is to check if the $tenantSlug is null before trying to query the database. If it's null, we just let the request pass through. The landing page doesn't need a tenant context.
// The correction:
if (!$tenantSlug) {
return $next($request);
}
$tenant = Tenant::where('slug', $tenantSlug)->first();
if (!$tenant) {
abort(404, 'Tenant not found.');
}
Applying the middleware strategically
You don't want this running on your API health checks or your login page (unless the login page is tenant-specific). I recommend creating a route group. By wrapping your "app" routes in this middleware, you ensure that by the time a request hits your OrderController, the tenant has already been validated and bound to the container.
Route::middleware([IdentifyTenant::class])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
Route::get('/inventory', [InventoryController::class, 'index']);
});
This approach turns the tenant identity into a "given." Your business logic can now simply assume the tenant exists, which makes your controllers significantly leaner.
📋 Practical Task
Implement a Header-Based Tenant Fallback
In some environments, subdomains aren't possible (like during local development or when using a mobile app via a proxy). Your task is to modify the IdentifyTenant middleware to support a fallback mechanism.
The Requirements:
- First, attempt to identify the tenant via the subdomain as we did in the lesson.
- If no subdomain is present, check the request headers for a key named
X-Tenant-ID. - If the header exists, use its value as the
$tenantSlug. - If neither a subdomain nor the header is present, allow the request to proceed to the next middleware (supporting the main site/landing page).
- If a slug was found (via either method) but no matching tenant exists in the database, throw the 404 error.
There are no comments for now.