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
213: Tenant-Specific Configuration Management
When you first move from building a single-client app to a multi-tenant SaaS, you'll hit a wall regarding configuration. In a standard app, you have a .env file or a config.php, and that's it. But when you have five hundred different companies using your software, and each one needs their own Stripe API key, a unique brand color for their dashboard, and a specific "feature flag" to enable a beta module, you can't just keep adding lines to a static file.
The Trap of the Giant Configuration Array
The first instinct for many developers is to create a massive associative array or a series of files named after the tenant. I've seen people do this: they create a tenants.php file where the keys are the tenant IDs and the values are arrays of their specific settings. It looks simple at first.
// The naive way
return [
'acme_corp' => [
'brand_color' => '#ff0000',
'stripe_key' => 'sk_test_123',
'max_users' => 50,
],
'globex' => [
'brand_color' => '#0000ff',
'stripe_key' => 'sk_test_456',
'max_users' => 100,
],
];
Here is why this is a disaster waiting to happen: it couples your infrastructure to your code. Every time you sign a new customer, you have to commit a code change and redeploy the entire application just to add a few configuration strings. If a customer wants to change their brand color via a settings page in your UI, you can't—because the configuration is hardcoded in a PHP file. You've essentially turned your version control system into a database, which is a recipe for merge conflicts and deployment anxiety.
Moving Logic into the Data Layer
The professional way to handle this is to treat tenant configuration as data, not code. You want a configuration manager—a service class—that resolves a setting based on the current tenant's context. I usually implement this as a "layered" lookup. You check for a tenant-specific override first; if that doesn't exist, you fall back to a global system default.
In this model, your settings live in a database table (usually a simple key-value pair table linked to a tenant_id). When you need a value, you ask your service for it. I prefer this because it allows you to build an admin panel where tenants can manage their own settings in real-time without you ever touching a terminal.
class TenantConfig {
private $tenantId;
private $db;
private $cache = [];
public function __construct(int $tenantId, PDO $db) {
$this->tenantId = $tenantId;
$this->db = $db;
}
public function get(string $key, $default = null) {
// Simple local cache to avoid hammering the DB in one request
if (isset($this->cache[$key])) {
return $this->cache[$key];
}
$stmt = $this->db->prepare("SELECT value FROM tenant_settings WHERE tenant_id = ? AND setting_key = ?");
$stmt->execute([$this->tenantId, $key]);
$value = $stmt->fetchColumn();
$this->cache[$key] = $value !== false ? $value : $default;
return $this->cache[$key];
}
}
The Performance Trade-off and the Cache Layer
Now, you might be thinking: "Wait, I'm now hitting the database every time I want to know what color the header should be?" You're right. That's the cost of flexibility. If you have a complex page that checks twenty different tenant settings, you've just added twenty queries to your request.
To solve this, don't just rely on the internal class cache I showed above. In a production environment, I'd wrap this in a PSR-16 cache (like Redis). When a tenant's settings are updated, you simply invalidate the cache key for that specific tenant. This gives you the best of both worlds: the speed of a static config file with the flexibility of a database. You're trading a small amount of architectural complexity for a system that can scale to thousands of tenants without slowing down.
📋 Practical Task
Build a Layered Tenant Configuration Resolver
Your goal is to create a ConfigResolver class that simulates the behavior of a production SaaS configuration system. You need to implement a system that checks for values in a specific order of priority: Tenant Override > Global Default > Hardcoded Fallback.
Requirements:
- Create a class
ConfigResolverthat accepts an array of$tenantSettings(simulating a DB result) and an array of$globalDefaultsin its constructor. - Implement a method
get(string $key, $fallback = null). - The method must return the value from
$tenantSettingsif it exists. - If not in tenant settings, it must return the value from
$globalDefaults. - If it's in neither, it must return the
$fallbackprovided in the method argument. - Include a test script that instantiates the resolver for a "Premium Tenant" (who has a custom
max_fileslimit) and a "Basic Tenant" (who relies on the global default), and print the results to the console to verify the hierarchy works.
There are no comments for now.