Procedural or OOP PHP for a dating website

5a4870f2407dabd7c7a1cccbb8191e49.png

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):

When to Choose OOP PHP (The "Looking for Marriage" Approach):

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!

Back to ChameleonSoftwareOnline.com