I run a handful of classifieds and listing sites: local classifieds, a global version, a services directory, a property site and a marketplace for websites. They look different and target different audiences, but they share one PHP codebase and one database cluster. Here's how that's structured and what I'd do the same way again.
Resolve the site from the host name
Every request starts by working out which site it belongs to. A small sites table holds the per-site config:
CREATE TABLE sites (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
host VARCHAR(190) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
theme VARCHAR(50) NOT NULL,
default_country CHAR(2) NULL,
root_category_id INT UNSIGNED NULL
);
<?php
$host = strtolower(preg_replace('/^www\./', '', $_SERVER['HTTP_HOST'] ?? ''));
$stmt = $pdo->prepare('SELECT * FROM sites WHERE host = ?');
$stmt->execute([$host]);
$site = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$site) {
http_response_code(404);
exit('Unknown site');
}
define('SITE_ID', (int) $site['id']);
Cache that lookup (APCu works well) so it isn't a query on every hit.
Scope data with a site column, not separate databases
Listings carry a site_id, plus an optional many-to-many table for listings that should also appear on sister sites. One schema means one migration path, and cross-posting a listing is just an extra row:
CREATE TABLE listing_sites (
listing_id INT UNSIGNED NOT NULL,
site_id INT UNSIGNED NOT NULL,
PRIMARY KEY (listing_id, site_id),
KEY (site_id)
);
Every listing query joins through listing_sites, so a site only ever sees what it's allowed to show.
Themes as thin template sets
Each site has its own templates folder that overrides a shared base. The lookup falls back to base/ when a site doesn't override a file, so most sites only customize the header, footer and home page.
Per-site SEO, shared logic
Titles, meta descriptions and canonical URLs come from the site config. When a listing appears on more than one site, the canonical points to the site where it was originally posted, which avoids competing with yourself in search.
What I'd watch out for
-
Never trust the Host header blindly. Only accept hosts that exist in
sites. - Keep sessions per host. Separate cookie domains stop a login on one site leaking to another unintentionally.
- Log the site ID with every error. "Search is broken" means nothing until you know which site.
One codebase has kept maintenance sane: a bug fixed once is fixed everywhere.
I build and run these sites, and listings can appear across the whole network: Classifieds24.net, Classifieds24.info, Classifieds24.org, GlobalClassifieds.info, GlobalClassifieds.org, HirePros.pro, Realtor.pm and LinkMarket.app.
Top comments (1)
One schema with a site column is the sane call. Curious how you handle moderation across the network, though: does a spam listing removed on one site get pulled from its sister sites automatically? Disclosure: we build a directory plugin (Listora), and stale or duplicate listings are what owners complain about most.