Procedural or OOP PHP for a dating website

Introduction: The Programming Paradigm Dating Game
Welcome, brave developer, to the ultimate dating showdown! No, we're not talking about helping users find their soulmates (though we'll get to that). We're talking about the programming equivalent of choosing between a meticulously organized dating profile with personality quizzes and compatibility algorithms versus scribbling your number on a napkin and hoping for the best.
In the red corner, weighing in with classes, objects, and encapsulation: Object-Oriented PHP (OOP)! And in the blue corner, the straightforward, no-nonsense contender: Procedural PHP!
Let's dive into this romantic comedy of errors and see which approach will help you build a dating site that doesn't end up being the programming equivalent of a bad first date.
Chapter 1: The First Date - Project Structure
Procedural PHP: The "Wing It" Approach
Picture this: you're building your dating site with procedural PHP. It's like showing up to a first date in sweatpants - comfortable, straightforward, but maybe not making the best impression long-term.
File Structure:
/dating-site/
├── config.php
├── functions.php
├── register.php
├── login.php
├── profile.php
├── search.php
├── messages.php
└── ... 47 other files that somehow all include each other
In config.php:
$db_host = 'localhost';
$db_user = 'root';
$db_pass = '';
$db_name = 'dating_site';
$connection = mysqli_connect($db_host, $db_user, $db_pass, $db_name);
if (!$connection) {
die("Connection failed: " . mysqli_connect_error());
}
function get_user_data($user_id) {
global $connection;
$query = "SELECT * FROM users WHERE id = " . mysqli_real_escape_string($connection, $user_id);
$result = mysqli_query($connection, $query);
return mysqli_fetch_assoc($result);
}
function update_profile($user_id, $data) {
global $connection;
// 200 lines of validation and SQL queries
// Hope you like scrolling!
}
function send_message($from_id, $to_id, $message) {
global $connection;
// Another 150 lines
// Did you remember to validate all inputs?
}
// 50 more functions that all need the $connection
Notice something? That global $connection is like that one friend who crashes every date - always there, always awkward. Procedural code quickly becomes a tangled web of dependencies where changing one function might break three others you forgot about.
OOP PHP: The "I Have a System" Approach
Now let's look at OOP. This is the developer who shows up with a color-coded dating strategy, compatibility spreadsheets, and a five-year plan.
File Structure:
/dating-site/
├── /src/
│ ├── Database/
│ │ └── Connection.php
│ ├── Entities/
│ │ ├── User.php
│ │ ├── Profile.php
│ │ └── Message.php
│ ├── Services/
│ │ ├── AuthService.php
│ │ ├── MatchService.php
│ │ └── MessageService.php
│ └── Managers/
│ ├── UserManager.php
│ └── ProfileManager.php
├── /views/
└── /public/
In User.php:
class User {
private $id;
private $username;
private $email;
private $passwordHash;
private $profile;
private $db;
public function __construct(Database $db) {
$this->db = $db;
}
public function register($username, $email, $password) {
// Validation logic
if (!$this->isValidEmail($email)) {
throw new InvalidArgumentException("That email looks faker than a reality TV romance!");
}
// Hash password
$this->passwordHash = password_hash($password, PASSWORD_DEFAULT);
// Save to database
return $this->save();
}
public function login($password) {
return password_verify($password, $this->passwordHash);
}
public function getProfile() {
if (!$this->profile) {
$this->profile = new Profile($this->db);
$this->profile->loadByUserId($this->id);
}
return $this->profile;
}
// And so on... each method has a single responsibility
}
See the difference? OOP is like having personal assistants for every task. The User object knows how to handle user-related stuff, the Profile object handles profile stuff, and they work together without stepping on each other's toes.
Chapter 2: The User Registration Tango
Procedural: The "Hope This Works" Method
// register.php
include 'config.php';
include 'functions.php';
include 'validation.php';
include 'email_templates.php';
include 'security.php';
// How many includes is too many includes?
session_start();
if ($_POST['register']) {
$username = $_POST['username'];
$email = $_POST['email'];
$password = $_POST['password'];
$confirm = $_POST['confirm_password'];
// Validation scattered everywhere
$errors = array();
if (empty($username)) {
$errors[] = "Username is required!";
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "That email is as invalid as a 'nice guy' profile!";
}
if ($password != $confirm) {
$errors[] = "Passwords don't match! This isn't Tinder - you can't be this indecisive!";
}
// Check if user exists
$check_query = "SELECT id FROM users WHERE username = '" . mysqli_real_escape_string($connection, $username) . "' OR email = '" . mysqli_real_escape_string($connection, $email) . "'";
$check_result = mysqli_query($connection, $check_query);
if (mysqli_num_rows($check_result) > 0) {
$errors[] = "Username or email already exists! Try being more original!";
}
if (empty($errors)) {
// Finally create user
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$insert_query = "INSERT INTO users (username, email, password_hash, created_at) VALUES ('" . mysqli_real_escape_string($connection, $username) . "', '" . mysqli_real_escape_string($connection, $email) . "', '" . $hashed_password . "', NOW())";
if (mysqli_query($connection, $insert_query)) {
$user_id = mysqli_insert_id($connection);
// Create profile
$profile_query = "INSERT INTO profiles (user_id) VALUES ($user_id)";
mysqli_query($connection, $profile_query);
// Send welcome email
$to = $email;
$subject = "Welcome to DateFinder!";
$message = "Hi $username, welcome to our site!";
mail($to, $subject, $message);
// Log them in
$_SESSION['user_id'] = $user_id;
header("Location: dashboard.php");
} else {
$errors[] = "Registration failed: " . mysqli_error($connection);
}
}
// Show errors if any
if (!empty($errors)) {
foreach ($errors as $error) {
echo "<div class='error'>$error</div>";
}
}
}
This procedural approach is like trying to cook a five-course meal in a single pot. Everything gets mixed together, and good luck figuring out why the validation failed six months later!
OOP: The "Divide and Conquer" Strategy
// RegistrationController.php
class RegistrationController {
private $userService;
private $emailService;
private $validator;
public function __construct(UserService $userService, EmailService $emailService, Validator $validator) {
$this->userService = $userService;
$this->emailService = $emailService;
$this->validator = $validator;
}
public function handleRegistration(Request $request) {
try {
$data = $request->getPostData();
// Validate using dedicated validator
$validationResult = $this->validator->validateRegistration($data);
if (!$validationResult->isValid()) {
return new Response(['errors' => $validationResult->getErrors()]);
}
// Create user using service
$user = $this->userService->createUser(
$data['username'],
$data['email'],
$data['password']
);
// Send welcome email
$this->emailService->sendWelcomeEmail($user);
// Log them in
$this->userService->loginUser($user);
return new Response(['success' => true, 'user' => $user]);
} catch (UserExistsException $e) {
return new Response(['errors' => ['This user already exists! Try being more unique than that pickup line!']]);
} catch (Exception $e) {
return new Response(['errors' => ['Something went wrong! Like that time I tried online dating!']]);
}
}
}
// UserService.php
class UserService {
private $userRepository;
private $profileService;
public function createUser($username, $email, $password) {
// Check if user exists
if ($this->userRepository->findByUsernameOrEmail($username, $email)) {
throw new UserExistsException("User already exists!");
}
$user = new User();
$user->setUsername($username);
$user->setEmail($email);
$user->setPassword($password);
// Save user
$this->userRepository->save($user);
// Create profile
$this->profileService->createDefaultProfile($user);
return $user;
}
}
OOP is like having a well-organized kitchen with different stations for prep, cooking, and plating. Each class has its specific job, and they work together harmoniously.
Chapter 3: The Profile Management Saga
Procedural: The "Everything But the Kitchen Sink" Method
// profile.php - all 800 lines of it!
function update_user_profile($user_id, $profile_data) {
global $connection;
// Validate age
if (!empty($profile_data['age'])) {
if ($profile_data['age'] < 18) {
return "Sorry, you need to be older than your dating profile claims!";
}
if ($profile_data['age'] > 120) {
return "Either you're a vampire or lying about your age!";
}
}
// Validate height
if (!empty($profile_data['height'])) {
if ($profile_data['height'] < 100 || $profile_data['height'] > 250) {
return "Unless you're a hobbit or NBA player, that height seems suspicious!";
}
}
// Validate interests
if (!empty($profile_data['interests'])) {
$interests = json_encode($profile_data['interests']);
// But wait, we need to sanitize it first...
$interests = mysqli_real_escape_string($connection, $interests);
}
// Update basic info
$query = "UPDATE profiles SET ";
$updates = array();
if (!empty($profile_data['bio'])) {
$bio = mysqli_real_escape_string($connection, $profile_data['bio']);
$updates[] = "bio = '$bio'";
}
if (!empty($profile_data['location'])) {
$location = mysqli_real_escape_string($connection, $profile_data['location']);
$updates[] = "location = '$location'";
}
// Repeat for 20 more fields...
if (!empty($updates)) {
$query .= implode(", ", $updates);
$query .= " WHERE user_id = $user_id";
if (!mysqli_query($connection, $query)) {
return "Profile update failed: " . mysqli_error($connection);
}
}
// Handle photos
if (!empty($_FILES['profile_picture'])) {
$photo_result = handle_photo_upload($user_id, $_FILES['profile_picture']);
if ($photo_result !== true) {
return $photo_result;
}
}
// Update search index
update_search_index($user_id);
// Clear cache
clear_profile_cache($user_id);
return true;
}
// And somewhere else in the file...
function handle_photo_upload($user_id, $file) {
// 150 lines of photo handling
// Check file type, size, resize, create thumbnails, update database...
}
function update_search_index($user_id) {
// Another 100 lines
}
function clear_profile_cache($user_id) {
// 50 more lines
}
This procedural approach is like trying to fit your entire dating history into a single text message. It's messy, hard to read, and good luck making changes later!
OOP: The "Specialized Dating Consultants" Approach
// ProfileManager.php
class ProfileManager {
private $profileRepository;
private $photoService;
private $searchService;
private $cacheService;
private $validator;
public function updateProfile(User $user, array $profileData) {
// Get the profile
$profile = $user->getProfile();
// Update fields using dedicated methods
if (isset($profileData['bio'])) {
$profile->setBio($profileData['bio']);
}
if (isset($profileData['location'])) {
$profile->setLocation($profileData['location']);
}
if (isset($profileData['interests'])) {
$profile->setInterests($profileData['interests']);
}
// Validate the profile
if (!$this->validator->validateProfile($profile)) {
throw new InvalidProfileException("Your profile is as invalid as that 'just here for friends' excuse!");
}
// Save to database
$this->profileRepository->save($profile);
// Update search index
$this->searchService->indexProfile($profile);
// Clear cache
$this->cacheService->clearProfileCache($user->getId());
return $profile;
}
}
// PhotoService.php
class PhotoService {
public function uploadProfilePhoto(User $user, UploadedFile $file) {
// Validate file
if (!$this->validatePhoto($file)) {
throw new InvalidPhotoException("That photo is more filtered than your dating expectations!");
}
// Process image
$processor = new ImageProcessor();
$photoPaths = $processor->processProfilePhoto($file);
// Save to database
$photo = new ProfilePhoto($user, $photoPaths);
$this->photoRepository->save($photo);
return $photo;
}
private function validatePhoto(UploadedFile $file) {
// Dedicated validation logic
return true;
}
}
// Profile.php
class Profile {
private $bio;
private $location;
private $interests = [];
private $photos;
public function setBio($bio) {
if (strlen($bio) > 500) {
throw new InvalidArgumentException("Your bio is longer than a bad first date! Keep it under 500 characters.");
}
$this->bio = $bio;
}
public function setInterests(array $interests) {
if (count($interests) > 20) {
throw new InvalidArgumentException("You have more interests than matches! Limit to 20.");
}
$this->interests = $interests;
}
public function addPhoto(ProfilePhoto $photo) {
if (count($this->photos) >= 10) {
throw new TooManyPhotosException("We get it, you're photogenic! But 10 photos is the limit.");
}
$this->photos[] = $photo;
}
}
OOP is like having a team of dating experts: one handles your profile text, another your photos, another your matching preferences. Each knows their job perfectly.
Chapter 4: The Matching Algorithm Marathon
Procedural: The "Spaghetti Matching" Method
// match_users.php - because why separate concerns when you can have everything in one file?
function find_matches($user_id, $max_distance = 50, $min_age = 18, $max_age = 99) {
global $connection;
// Get user data
$user_query = "SELECT * FROM users u
LEFT JOIN profiles p ON u.id = p.user_id
WHERE u.id = $user_id";
$user_result = mysqli_query($connection, $user_query);
$user = mysqli_fetch_assoc($user_result);
// Build the query based on 15 different criteria
$match_query = "SELECT u.*, p.*,
(SELECT COUNT(*) FROM likes l WHERE l.liked_user_id = u.id AND l.user_id = $user_id) as already_liked,
(3959 * acos(cos(radians(" . $user['latitude'] . ")) * cos(radians(p.latitude)) *
cos(radians(p.longitude) - radians(" . $user['longitude'] . ")) +
sin(radians(" . $user['latitude'] . ")) * sin(radians(p.latitude)))) AS distance ";
$match_query .= "FROM users u
LEFT JOIN profiles p ON u.id = p.user_id
WHERE u.id != $user_id
AND u.is_active = 1
AND u.is_verified = 1
AND u.gender IN (" . get_preferred_genders($user['looking_for']) . ") ";
// Age range
$match_query .= "AND TIMESTAMPDIFF(YEAR, u.date_of_birth, CURDATE()) BETWEEN $min_age AND $max_age ";
// Distance
$match_query .= "HAVING distance < $max_distance ";
// Exclude blocked users
$match_query .= "AND u.id NOT IN (SELECT blocked_user_id FROM blocked_users WHERE user_id = $user_id) ";
// Order by some complex algorithm
$match_query .= "ORDER BY (
(CASE WHEN p.relationship_goal = '" . $user['relationship_goal'] . "' THEN 50 ELSE 0 END) +
(CASE WHEN p.has_children = '" . $user['wants_children'] . "' THEN 30 ELSE 0 END) +
(SELECT COUNT(*) FROM common_interests WHERE user_id = u.id AND interest_id IN
(SELECT interest_id FROM user_interests WHERE user_id = $user_id)) * 10 -
distance * 0.5
) DESC
LIMIT 20";
$match_result = mysqli_query($connection, $match_query);
$matches = array();
while ($match = mysqli_fetch_assoc($match_result)) {
// Calculate compatibility score
$compatibility = calculate_compatibility($user, $match);
$match['compatibility'] = $compatibility;
$matches[] = $match;
}
return $matches;
}
function calculate_compatibility($user1, $user2) {
$score = 0;
// Interest matching
$common_interests = array_intersect(
json_decode($user1['interests'], true),
json_decode($user2['interests'], true)
);
$score += count($common_interests) * 5;
// Lifestyle compatibility
if ($user1['smoking'] == $user2['smoking']) $score += 10;
if ($user1['drinking'] == $user2['drinking']) $score += 10;
// Relationship goals
if ($user1['relationship_goal'] == $user2['relationship_goal']) $score += 20;
// Distance penalty
$distance = $user2['distance'];
if ($distance > 100) $score -= 20;
elseif ($distance > 50) $score -= 10;
return min($score, 100);
}
This procedural matching code is like trying to find your soulmate by randomly swiping on everyone in a 50-mile radius. It works, but it's not exactly efficient or maintainable.
OOP: The "Compatibility Science Lab" Approach
// MatchService.php
class MatchService {
private $userRepository;
private $matchCalculator;
private $filterChain;
private $scoringStrategy;
public function findMatches(User $user, MatchCriteria $criteria) {
// Get potential matches
$potentialMatches = $this->userRepository->findPotentialMatches($user, $criteria);
// Apply filters
$filteredMatches = $this->filterChain->apply($user, $potentialMatches);
// Calculate compatibility scores
$scoredMatches = array_map(function($match) use ($user) {
return new MatchResult(
$match,
$this->matchCalculator->calculateCompatibility($user, $match),
$this->matchCalculator->getCompatibilityReasons($user, $match)
);
}, $filteredMatches);
// Sort by score
usort($scoredMatches, function($a, $b) {
return $b->getScore() <=> $a->getScore();
});
return array_slice($scoredMatches, 0, $criteria->getLimit());
}
}
// MatchCalculator.php
class MatchCalculator {
private $scoringStrategies;
public function calculateCompatibility(User $user1, User $user2) {
$totalScore = 0;
$maxPossibleScore = 0;
foreach ($this->scoringStrategies as $strategy) {
$strategyScore = $strategy->calculateScore($user1, $user2);
$totalScore += $strategyScore;
$maxPossibleScore += $strategy->getMaxScore();
}
return $maxPossibleScore > 0 ? ($totalScore / $maxPossibleScore) * 100 : 0;
}
}
// InterestScoringStrategy.php
class InterestScoringStrategy implements ScoringStrategy {
public function calculateScore(User $user1, User $user2) {
$interests1 = $user1->getProfile()->getInterests();
$interests2 = $user2->getProfile()->getInterests();
$commonInterests = array_intersect($interests1, $interests2);
$score = count($commonInterests) * 5;
return min($score, $this->getMaxScore());
}
public function getMaxScore() {
return 25; // Maximum 25 points for interests
}
}
// LifestyleScoringStrategy.php
class LifestyleScoringStrategy implements ScoringStrategy {
public function calculateScore(User $user1, User $user2) {
$score = 0;
$profile1 = $user1->getProfile();
$profile2 = $user2->getProfile();
if ($profile1->getSmoking() === $profile2->getSmoking()) {
$score += 10;
}
if ($profile1->getDrinking() === $profile2->getDrinking()) {
$score += 10;
}
if ($profile1->getHasChildren() === $profile2->getWantsChildren()) {
$score += 15;
}
return min($score, $this->getMaxScore());
}
public function getMaxScore() {
return 35;
}
}
// DistanceFilter.php
class DistanceFilter implements Filter {
public function filter(User $user, array $potentialMatches) {
$maxDistance = 100; // miles
return array_filter($potentialMatches, function($match) use ($user, $maxDistance) {
$distance = $this->calculateDistance(
$user->getProfile()->getLocation(),
$match->getProfile()->getLocation()
);
return $distance <= $maxDistance;
});
}
private function calculateDistance(Location $loc1, Location $loc2) {
// Calculate distance between two locations
return $loc1->distanceTo($loc2);
}
}
OOP matching is like having a team of relationship experts analyzing every aspect of compatibility. Each scoring strategy focuses on one area, filters remove unsuitable matches, and everything works together harmoniously.
Chapter 5: The Messaging System Melee
Procedural: The "Hope You Like Global Variables" Method
// messages.php - where function parameters go to die!
$connection = mysqli_connect(...); // Already defined in config.php? Who knows!
function send_message($from_id, $to_id, $message, $subject = '') {
global $connection;
// Validate inputs
if (empty($from_id) || empty($to_id) || empty($message)) {
return "Missing required fields! This isn't ghosting - fill out the form!";
}
// Check if users exist
$from_user = get_user_data($from_id);
$to_user = get_user_data($to_id);
if (!$from_user || !$to_user) {
return "User not found! Did they unmatch you already?";
}
// Check if blocked
if (is_blocked($from_id, $to_id)) {
return "You can't message this user! Take the hint!";
}
// Check rate limiting
if (is_rate_limited($from_id, 'messages')) {
return "Slow down there, Casanova! You're sending messages too quickly!";
}
// Sanitize message
$clean_message = mysqli_real_escape_string($connection, $message);
$clean_subject = mysqli_real_escape_string($connection, $subject);
// Insert message
$query = "INSERT INTO messages (sender_id, receiver_id, subject, message, sent_date)
VALUES ($from_id, $to_id, '$clean_subject', '$clean_message', NOW())";
if (mysqli_query($connection, $query)) {
// Send notification
send_notification($to_id, 'new_message', $from_id);
// Update conversation cache
update_conversation_cache($from_id, $to_id);
return true;
} else {
return "Failed to send message: " . mysqli_error($connection);
}
}
function get_conversation($user1_id, $user2_id, $limit = 50) {
global $connection;
$query = "SELECT m.*, u1.username as sender_name, u2.username as receiver_name
FROM messages m
JOIN users u1 ON m.sender_id = u1.id
JOIN users u2 ON m.receiver_id = u2.id
WHERE (m.sender_id = $user1_id AND m.receiver_id = $user2_id)
OR (m.sender_id = $user2_id AND m.receiver_id = $user1_id)
ORDER BY m.sent_date DESC
LIMIT $limit";
$result = mysqli_query($connection, $query);
$messages = array();
while ($row = mysqli_fetch_assoc($result)) {
$messages[] = $row;
}
return array_reverse($messages); // Because we want chronological order
}
// And 15 more message-related functions in the same file...
This procedural messaging system is like trying to have a conversation while shouting across a crowded room. Everything gets mixed up, and good luck following the thread!
OOP: The "Organized Flirting" System
// MessageService.php
class MessageService {
private $messageRepository;
private $userService;
private $notificationService;
private $rateLimiter;
private $validator;
public function sendMessage(User $sender, User $receiver, MessageContent $content) {
// Validate can send message
if (!$this->canSendMessage($sender, $receiver)) {
throw new MessageException("You cannot send messages to this user. Maybe try improving your profile first!");
}
// Check rate limiting
if ($this->rateLimiter->isLimited($sender, 'messages')) {
throw new RateLimitException("Slow down there! You're messaging like it's happy hour!");
}
// Create and save message
$message = new Message($sender, $receiver, $content);
$this->messageRepository->save($message);
// Send notification
$this->notificationService->notifyNewMessage($receiver, $message);
return $message;
}
private function canSendMessage(User $sender, User $receiver) {
return !$this->userService->isBlocked($sender, $receiver) &&
$receiver->allowsMessagesFrom($sender);
}
public function getConversation(User $user1, User $user2, int $limit = 50) {
return $this->messageRepository->findConversation($user1, $user2, $limit);
}
}
// Message.php
class Message {
private $id;
private $sender;
private $receiver;
private $content;
private $sentDate;
private $isRead = false;
public function __construct(User $sender, User $receiver, MessageContent $content) {
$this->sender = $sender;
$this->receiver = $receiver;
$this->content = $content;
$this->sentDate = new DateTime();
}
public function markAsRead() {
$this->isRead = true;
}
public function canBeViewedBy(User $user) {
return $user->getId() === $this->sender->getId() ||
$user->getId() === $this->receiver->getId();
}
}
// MessageContent.php
class MessageContent {
private $subject;
private $body;
private $type; // text, icebreaker, etc.
public function __construct($body, $subject = null, $type = 'text') {
$this->body = $this->sanitizeBody($body);
$this->subject = $subject;
$this->type = $type;
if (!$this->isValid()) {
throw new InvalidMessageException("That message is emptier than your match queue!");
}
}
private function sanitizeBody($body) {
$clean = trim(strip_tags($body));
if (strlen($clean) > 1000) {
throw new InvalidMessageException("Your message is longer than a bad dating profile novel! Keep it under 1000 characters.");
}
return $clean;
}
private function isValid() {
return !empty($this->body);
}
}
// Conversation.php
class Conversation {
private $participants = [];
private $messages = [];
private $unreadCount = 0;
public function __construct(array $participants) {
if (count($participants) !== 2) {
throw new InvalidArgumentException("A conversation needs exactly 2 participants! This isn't a group date!");
}
$this->participants = $participants;
}
public function addMessage(Message $message) {
if (!$this->includesUser($message->getSender()) ||
!$this->includesUser($message->getReceiver())) {
throw new InvalidArgumentException("Message participants don't match conversation participants!");
}
$this->messages[] = $message;
if (!$message->isRead() && $message->getReceiver()->isCurrentUser()) {
$this->unreadCount++;
}
}
public function getLastMessage() {
return end($this->messages) ?: null;
}
public function markAsRead() {
foreach ($this->messages as $message) {
if ($message->getReceiver()->isCurrentUser()) {
$message->markAsRead();
}
}
$this->unreadCount = 0;
}
}
OOP messaging is like having a professional dating coordinator who ensures every message is appropriate, timely, and delivered perfectly.
Chapter 6: The Maintenance Marathon
Procedural: The "Pray It Doesn't Break" Strategy
Six months later, you need to add read receipts to messages:
// Find all the places where messages are handled...
// Wait, was it in messages.php? Or was it in user_functions.php?
// Let me search for "INSERT INTO messages"...
// 47 results later...
// Now I need to update every function that deals with messages
// Hope I don't miss any!
function send_message($from_id, $to_id, $message, $subject = '') {
// ... all the existing code ...
// Add read receipt field
$query = "INSERT INTO messages (sender_id, receiver_id, subject, message, sent_date, is_read)
VALUES ($from_id, $to_id, '$clean_subject', '$clean_message', NOW(), 0)";
// ... rest of function
}
function get_conversation($user1_id, $user2_id, $limit = 50) {
// Update query to include is_read
$query = "SELECT m.*, u1.username as sender_name, u2.username as receiver_name
FROM messages m
JOIN users u1 ON m.sender_id = u1.id
JOIN users u2 ON m.receiver_id = u2.id
WHERE (m.sender_id = $user1_id AND m.receiver_id = $user2_id)
OR (m.sender_id = $user2_id AND m.receiver_id = $user1_id)
ORDER BY m.sent_date DESC
LIMIT $limit";
// ... and update the display logic in 3 different template files
}
// Don't forget to update the message display in inbox.php, conversation.php, and notifications.php!
// This could take days...
Procedural maintenance is like trying to untangle Christmas lights after they've been in storage for a year. Good luck!
OOP: The "Elegant Evolution" Approach
With OOP, adding read receipts is much cleaner:
// Just update the Message class
class Message {
// ... existing properties ...
private $isRead = false;
private $readDate = null;
public function markAsRead() {
$this->isRead = true;
$this->readDate = new DateTime();
}
public function isRead() {
return $this->isRead;
}
public function getReadDate() {
return $this->readDate;
}
}
// Update the repository to handle the new field
class MessageRepository {
public function save(Message $message) {
// The save method already handles all message properties
// No changes needed if using an ORM or proper data mapping
}
}
// Add a method to the service
class MessageService {
public function markMessageAsRead(Message $message, User $reader) {
if ($message->canBeViewedBy($reader) && $message->getReceiver()->equals($reader)) {
$message->markAsRead();
$this->messageRepository->save($message);
return true;
}
return false;
}
}
That's it! The changes are isolated to the relevant classes, and everything else continues to work seamlessly.
Chapter 7: The Testing Tango
Procedural: The "Cross Your Fingers" Method
// How do you even test this?
function update_user_profile($user_id, $profile_data) {
global $connection;
// 200 lines of mixed validation, database operations, and business logic
// Good luck unit testing this!
// You'd need to:
// 1. Mock the global database connection (impossible)
// 2. Set up a test database with specific data
// 3. Hope nothing else in the system breaks
// 4. Pray to the coding gods
}
// Integration test that touches everything
function test_profile_update() {
// Set up test data
$user_id = create_test_user();
$profile_data = array(
'bio' => 'Test bio',
'location' => 'Testville'
);
// Call the function
$result = update_user_profile($user_id, $profile_data);
// Check the database directly
$check_query = "SELECT * FROM profiles WHERE user_id = $user_id";
// ... and so on
// This tests everything at once, but when it fails...
// Which part broke? Validation? Database? File uploads? Who knows!
}
Procedural testing is like blind dating - you never know what you're going to get until it's too late!
OOP: The "Confident Dating" Approach
// Easy to test each class in isolation
class ProfileManagerTest {
public function testUpdateProfile() {
// Mock dependencies
$mockRepo = $this->createMock(ProfileRepository::class);
$mockValidator = $this->createMock(ProfileValidator::class);
// Set up expectations
$mockValidator->expects($this->once())
->method('validate')
->willReturn(true);
$mockRepo->expects($this->once())
->method('save');
// Test the actual method
$manager = new ProfileManager($mockRepo, $mockValidator);
$user = new User(1, 'testuser');
$profile = new Profile($user);
$result = $manager->updateProfile($profile, ['bio' => 'New bio']);
$this->assertEquals('New bio', $profile->getBio());
}
}
class MessageServiceTest {
public function testCannotSendMessageToBlockedUser() {
$sender = new User(1, 'sender');
$receiver = new User(2, 'receiver');
$mockUserService = $this->createMock(UserService::class);
$mockUserService->method('isBlocked')
->willReturn(true); // They're blocked!
$service = new MessageService(
$this->createMock(MessageRepository::class),
$mockUserService,
$this->createMock(NotificationService::class)
);
$this->expectException(MessageException::class);
$this->expectExceptionMessage("You cannot send messages to this user");
$service->sendMessage($sender, $receiver, new MessageContent("Hey there!"));
}
}
OOP testing is like having a dating coach who prepares you for every scenario. You know exactly what to expect and can handle any situation confidently!
Conclusion: And the Winner Is...
After this epic dating-style showdown between OOP and procedural PHP for building a dating website, you might be expecting a clear winner. But much like real dating, the answer is: it depends on your situation!
When to Choose Procedural PHP (The "Casual Dating" Approach):
- Small projects: If you're building a simple dating site for a niche community
- Quick prototypes: When you need to test an idea rapidly
- Limited timeline: When you have more deadlines than matches on a Monday night
- Solo development: When you're the only developer and won't be hit by a bus
- Learning projects: Great for understanding the fundamentals before diving into OOP
When to Choose OOP PHP (The "Looking for Marriage" Approach):
- Large-scale applications: When you're building the next Tinder
- Team development: When multiple developers need to work together without stepping on each other's toes
- Long-term maintenance: When you want your code to be maintainable longer than most relationships last
- Complex business logic: When your matching algorithm needs more sophistication than "swipe right"
- Testing requirements: When you need reliable, automated tests
- Future scalability: When you plan to add features like video calls, AI matching, or blockchain-based commitment contracts (because why not?)
The Real Winner: Using Both Wisely
The smartest approach? Use both! Many modern PHP applications use OOP for the core business logic and procedural patterns where they make sense:
// OOP for complex business logic
class DatingApp {
private $userService;
private $matchService;
private $messageService;
// All the organized, testable goodness
}
// Procedural for simple utilities
function format_age_range($min_age, $max_age) {
if ($min_age == 18 && $max_age == 99) {
return "Any age";
}
return "{$min_age} - {$max_age}";
}
function get_distance_description($miles) {
if ($miles < 1) return "Less than a mile away";
if ($miles < 5) return "Within 5 miles";
if ($miles < 25) return "Within 25 miles";
return "{$miles} miles away";
}
Final Thoughts
Building a dating website with procedural PHP is like having a wild, passionate fling - exciting at first, but potentially messy and hard to maintain. Building with OOP is like a committed relationship - it requires more work upfront, but provides stability and grows better over time.
Whichever approach you choose, remember: the best code, like the best relationships, is built with care, attention to detail, and the willingness to adapt when things get complicated. Now go forth and code some love (or at least some functional dating websites)!
Procedural PHP: Great for a weekend fling project OOP PHP: Ready for a long-term commitment
Choose wisely, and may your code be as bug-free as your dating profile is honest!