Practical use of PHP __get, __set, __isset, and __unset Magic method

Practical use of PHP __get, __set, __isset, and __unset Magic methods

__get, __set, __isset, এবং __unset এর মত Magic Methods গুলি পিএইচপি-তে powerful tools যা আপনাকে একটি অবজেক্টের উপর কিছু নির্দিষ্ট operations কে আটকাতে এবং নিয়ন্ত্রণ করতে দেয়। এখানে কিছু বাস্তব-জীবনের পরিস্থিতি রয়েছে যেখানে আপনি এই magic methods গুলিকে প্রয়োজনে ব্যবহার করতে পারেন:

১. Data Validation and Transformation:

  • property assignments এ validation rules প্রয়োগ করতে __set Magic Method ব্যবহার করতে পারেন । উদাহরণস্বরূপ, আপনি নিশ্চিত করতে পারেন যে একটি age property একটি integer এবং শূন্যের চেয়ে বড়।
  • এছাড়া অ্যাসাইনমেন্টের সময় Transform বা sanitize করতে পারেন , যেমন date string কে DateTime object এ রূপান্তর করা।

Handling user profiles with validation and transformation of properties, including enhanced password validation rules.

আমরা এখন UserProfile নামে একটি ক্লাস তৈরি করব , যা একটি ওয়েব অ্যাপ্লিকেশনে ব্যবহারকারীর প্রোফাইলের রিপ্রেজেন্ট করার জন্য ডিজাইন করা হয়েছে। এটি username, email, birthdate এবং password এর মতো ফীচার গুলির জন্য validation এবং transformation নিয়ম rules করে৷ ক্লাসটি ব্যবহারকারীর ডেটা নিরাপদে হ্যান্ডলিং করার জন্য, ধারাবাহিকতা নিশ্চিত করতে এবং একটি ডাটাবেসে স্টোরেজ সুবিধার জন্য একটি robust solution প্রদান করে।

তবে তার আগে , নিম্নোক্ত Table Structure টি তৈরি করে নিন :

CREATE TABLE user_profiles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(20) NOT NULL,
    email VARCHAR(255) NOT NULL,
    birthdate DATE NOT NULL,
    password VARCHAR(255) NOT NULL
);

তো চলুন শুরু করা যাক :

<?php

/**
 * Class UserProfile
 *
 * Represents user profiles with validation and transformation of properties.
 */
class UserProfile
{
    private $db;
    private $data = [];
    public function __construct(PDO $db)
    {
        $this->db = $db;
    }

    /**
     * Magic method to set property values with validation and transformation.
     *
     * @param string $name  The name of the property.
     * @param mixed  $value The value to set.
     *
     * @throws InvalidArgumentException If validation fails.
     */
    public function __set($name, $value)
    {
        switch ($name) {
            case 'username':
                // Enforce validation rules for the username
                if (!is_string($value) || strlen($value) < 5 || strlen($value) > 20) {
                    throw new InvalidArgumentException("Invalid value for username property. Must be a string with length between 5 and 20 characters.");
                }
            
                $usernameQuery = "SELECT COUNT(*) FROM user_profiles WHERE username = :username";
                $usernameStatement = $this->db->prepare($usernameQuery);
                $usernameStatement->bindParam(':username', $value); // Change here: Use $value instead of $this->data['username']
                $usernameStatement->execute();
                $usernameCount = $usernameStatement->fetchColumn();
            
                if ($usernameCount > 0) {
                    throw new InvalidArgumentException("Username already exists.");
                }
                break;
            

            case 'email':
                // Enforce validation rules for email
                if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                    throw new InvalidArgumentException("Invalid email format for email property.");
                }
                
                // Check uniqueness for email
                $emailQuery = "SELECT COUNT(*) FROM user_profiles WHERE email = :email";
                $emailStatement = $this->db->prepare($emailQuery);
                $emailStatement->bindParam(':email', $value);
                $emailStatement->execute();
                $emailCount = $emailStatement->fetchColumn();

                if ($emailCount > 0) {
                    throw new InvalidArgumentException("Email already exists.");
                }


                break;

            case 'birthdate':
                // Transform and sanitize data: Convert date string to DateTime object
                try {
                    $dobDateTime = new DateTime($value);
                    $value = $dobDateTime;
                } catch (Exception $e) {
                    throw new InvalidArgumentException("Invalid date format for birthdate property. Expected format: Y-m-d");
                }
                break;

            case 'password':
                // Enforce validation rules for password
                if (strlen($value) < 8) {
                    throw new InvalidArgumentException("Password must be at least 8 characters long.");
                }

                if (!preg_match('/[A-Z]/', $value)) {
                    throw new InvalidArgumentException("Password must contain at least one uppercase letter.");
                }

                if (!preg_match('/[a-z]/', $value)) {
                    throw new InvalidArgumentException("Password must contain at least one lowercase letter.");
                }

                if (!preg_match('/\d/', $value)) {
                    throw new InvalidArgumentException("Password must contain at least one digit.");
                }

                if (!preg_match('/[^A-Za-z0-9]/', $value)) {
                    throw new InvalidArgumentException("Password must contain at least one special character.");
                }

                // Transform and sanitize data: Hash the password
                $value = password_hash($value, PASSWORD_DEFAULT);
                break;

            // Default case: Allow setting dynamic properties without validation or transformation
            default:
                $this->data[$name] = $value;
                return;
        }

        $this->data[$name] = $value;
    }

    /**
     * Magic method to get property values.
     *
     * @param string $name The name of the property.
     *
     * @return mixed The value of the property.
     *
     * @throws Exception If the property does not exist.
     */
    public function __get($name)
    {
        if (array_key_exists($name, $this->data)) {
            return $this->data[$name];
        } else {
            throw new Exception("Property $name does not exist");
        }
    }

    /**
     * Magic method to check if a property is set.
     *
     * @param string $name The name of the property.
     *
     * @return bool True if the property is set, false otherwise.
     */
    public function __isset($name)
    {
        return isset($this->data[$name]);
    }

    /**
     * Magic method to unset a property.
     *
     * @param string $name The name of the property.
     */
    public function __unset($name)
    {
        if (isset($this->data[$name])) {
            unset($this->data[$name]);
        }
    }

    /**
     * Method to save the user profile data to the database.
     *
     * @throws PDOException If there's an issue with the database connection or query execution.
     */
    public function saveToDatabase()
    {
        
        // Prepare and execute a query to save the user profile data
        $query = "INSERT INTO user_profiles (username, email, birthdate, password) VALUES (:username, :email, :birthdate, :password)";
        $statement = $this->db->prepare($query);

        // Bind parameters
        $statement->bindParam(':username', $this->data['username']);
        $statement->bindParam(':email', $this->data['email']);
        $birthdate = $this->data['birthdate']->format('Y-m-d');
        $statement->bindParam(':birthdate', $birthdate);

        $statement->bindParam(':password', $this->data['password']);

        // Execute the query
        $statement->execute();
    }
}

// Example usage:

// Assume there's a database connection
try {
$db = new PDO('mysql:host=localhost;dbname=eshop', 'root', '');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$userProfile = new UserProfile($db);

    // Set valid username
    $userProfile->username = 'johndoe1';

    // Set valid email
    $userProfile->email = 'john.doe@example.com';

    // Set valid birthdate
    $userProfile->birthdate = '1990-05-15';

    // Set and hash the password
    $userProfile->password = 'Secure123!';

    // Save the user profile to the database
    $userProfile->saveToDatabase();

} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

এই বাস্তব উদাহরণে:

  • UserProfile class class টি user profile data হ্যান্ডলিং এর জন্য দায়িত্বপ্রাপ্ত।
  • __set Magic Method validation rules প্রয়োগ করে এবং username, email, birthdate এবং password এর মতো ফীচার গুলিতে data transformation করে।
  • saveToDatabase method টি একটি ডাটাবেসে validated এবং transformed user profile data সংরক্ষণ করে।

এই scenario টি ওয়েব ডেভেলপমেন্টে একটি common use case অনুকরণ করে যেখানে ইউজারের ইনপুটকে একটি ডাটাবেসে সংরক্ষণ করার আগে যাচাই করা এবং রূপান্তরিত করা প্রয়োজন। UserProfile class ডেটা হ্যান্ডলিংয়ের জন্য logic কে এনক্যাপসুলেট করে, নিশ্চিত করে যে সংরক্ষিত ডেটা সামঞ্জস্যপূর্ণ, ভ্যালিড এবং সিকিউর।

২. Lazy Loading:

  • __get Magic Method ব্যবহার করে একটি database বা external service থেকে ডেটা গুলোকে বের করতে আপনি lazy loading সুবিধা প্রয়োগ করতে পারেন।
  • আপনি তখনি ডেটা লোড করুন শুধুমাত্র যখন এটি আসলে অ্যাক্সেস করার দরকার হয়, এতে আপনার প্রজেক্টের পারফরমেন্স উন্নত হয়।

Lazy loading হল এমন একটি কৌশল যেখানে আপনি একটি অবজেক্টের ডেটা লোড করতে বিলম্ব করতে পারেন যতক্ষণ না এটি আসলে প্রয়োজনীয়। এটি বিশেষভাবে উপকারী হতে পারে যখন বড় ডেটাসেটগুলির সাথে ডিল করার সময় বা বাহ্যিক উত্স থেকে ডেটা আনার সময়, কারণ এটি শুধুমাত্র প্রয়োজনীয় তথ্য লোড করার মাধ্যমে কর্মক্ষমতা উন্নত করতে সহায়তা করে৷

আসুন একটি User class দিয়ে একটি বাস্তব উদাহরণ বিবেচনা করি যেখানে ব্যবহারকারীর বিবরণ একটি ডাটাবেসে সংরক্ষণ করা হয় এবং আমরা ব্যবহারকারীর পোস্টগুলির জন্য Lazy loading সুবিধা প্রয়োগ করতে চাই৷ পোস্টগুলি ডাটাবেসে একটি পৃথক টেবিলে সংরক্ষণ করা হয়।

চলুন প্রথমে নিম্নোক্ত দুটি টেবিল তৈরি করে ফেলি :

-- User table
CREATE TABLE users (
    user_id INT PRIMARY KEY,
    username VARCHAR(255) NOT NULL,
    password VARCHAR(25) NOT NULL
);

-- Post table
CREATE TABLE posts (
    post_id INT PRIMARY KEY,
    user_id INT,
    title VARCHAR(255) NOT NULL,
    content TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);

-- Sample data for users table
INSERT INTO users (user_id, username, password) VALUES
(1, 'john_doe', 'password123'),
(2, 'alice_smith', 'securePass'),
(3, 'bob_jones', 'pass987');

-- Sample data for posts table
INSERT INTO posts (post_id, user_id, title, content, created_at) VALUES
(1, 1, 'Introduction', 'Hello, this is my first post!', '2023-01-01 12:00:00'),
(2, 2, 'Tips for Coding', 'Here are some coding tips...', '2023-02-05 14:30:00'),
(3, 1, 'Travel Adventures', 'Exploring new places is always exciting.', '2023-03-10 10:45:00'),
(4, 3, 'Technology Trends', 'Discussing the latest in tech...', '2023-04-15 18:20:00');

এবার আপনি নিম্নোক্ত কোড গুলো লিখে ফেলুন :

<?php

class User
{
    private $userId;
    private $username;
    private $db;
    private $loadedPosts = false; // Track whether posts have been loaded
    private $posts; // Declare the posts property

    public function __construct(PDO $db, $userId)
    {
        $this->db = $db;
        $this->userId = $userId;

        // Load basic user details immediately
        $this->loadUserData();
    }

    private function loadUserData()
    {
        // Fetch basic user details from the database
        $query = "SELECT user_id, username FROM users WHERE user_id = :userId";
        $statement = $this->db->prepare($query);
        $statement->bindParam(':userId', $this->userId, PDO::PARAM_INT);
        $statement->execute();

        $userData = $statement->fetch(PDO::FETCH_ASSOC);

        if ($userData) {
            $this->username = $userData['username'];
        } else {
            throw new Exception("User not found");
        }
    }

    public function __get($property)
    {
        switch ($property) {
            case 'posts':
                // Implement lazy loading for user posts
                $this->loadUserPosts();
                return $this->$property;
            default:
                throw new Exception("Undefined property: $property");
        }
    }

    public function __isset($property)
    {
        switch ($property) {
            case 'posts':
                // Check if posts have been loaded
                return $this->loadedPosts;
            default:
                return false;
        }
    }

    public function __unset($property)
    {
        switch ($property) {
            case 'posts':
                // Unset the posts property
                unset($this->$property);
                $this->loadedPosts = false;
                break;
            default:
                throw new Exception("Undefined property: $property");
        }
    }


    private function loadUserPosts()
    {
    // Only load posts if they haven't been loaded before
        if (!$this->loadedPosts) {
            // Fetch user posts from the database only when accessed
            $query = "SELECT post_id, title, content FROM posts WHERE user_id = :userId";
            $statement = $this->db->prepare($query);
            $statement->bindParam(':userId', $this->userId, PDO::PARAM_INT);
            $statement->execute();

            $userPosts = $statement->fetchAll(PDO::FETCH_ASSOC);
            $this->posts = $userPosts;
            $this->loadedPosts = true; // Mark posts as loaded
        }
    }
}

// Example usage:


try {
    $db = new PDO('mysql:host=localhost;dbname=late_static', 'root', '');
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // Assume $db is a PDO instance connected to your database
    $user = new User($db, 1);
    
    // Accessing the 'posts' property triggers lazy loading
    $posts = $user->posts;
    
    // Check if posts are set
    if (isset($user->posts)) {
        // Do something with $user->posts
        foreach ($user->posts as $post) {
            echo "Post ID: {$post['post_id']}, Title: {$post['title']}, Content: {$post['content']}<br>";
        }
    } else {
        echo "Posts not loaded yet.";
    }
    
    // Unset the 'posts' property
    unset($user->posts);
    
    // Try to access the 'posts' property again
    // This will trigger lazy loading again
    if (isset($user->posts)) {
        // Do something with $user->posts
        foreach ($user->posts as $post) {
            echo "Post ID: {$post['post_id']}, Title: {$post['title']}, Content: {$post['content']}<br>";
        }
    } else {
        echo "Posts not loaded yet.";
    }    
}catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

এই উদাহরণে:

  • User class একটি __get magic method রয়েছে যা posts property এর জন্য lazy loading প্রয়োগ করে।
  • যখন posts property অ্যাক্সেস করা হয়, loadUserPosts method কল করা হয়, শুধুমাত্র প্রয়োজনের সময় ডাটাবেস থেকে ইউজারের পোস্ট আনা হবে।
  • basic user details অবিলম্বে constructor (loadUserData method) দিয়ে লোড করা হয়, কিন্তু পোস্টগুলি lazily লোড হয়।
  • __isset: এই method টি আপনাকে একটি property বিদ্যমান কিনা তা পরীক্ষা করার সুযোগ দেয়। এই উদাহরণে, পোস্টগুলি লোড করা হয়েছে কিনা তা নির্ধারণ করতে এটি ব্যবহার করা হয় ($this->loadedPosts)।
  • __unset: কোনো সম্পত্তি আনসেট করার চেষ্টা করার সময় এই method টি ব্যবহার করা হয়। এই ক্ষেত্রে, এটি ‘posts’ property আনসেট করে এবং $this->loadedPosts flag পুনরায় সেট করে।

এই পদ্ধতিটি অতিরিক্ত ডেটা (ব্যবহারকারীর পোস্ট) আনার মাধ্যমে পারফরম্যান্সকে অপ্টিমাইজ করে যখন এটি স্পষ্টভাবে অনুরোধ করা হয়, অপ্রয়োজনীয় ডাটাবেস ক্যোয়ারী কমিয়ে দেয়।

৩. Database Interaction: Create an ORM (Object-Relational Mapping)

ডাটাবেস রেকর্ডের সাথে কাজ করার জন্য একটি সুবিধাজনক এবং readable syntax প্রদান করার জন্য PDO ডাটাবেস দৃষ্টিকোণ সহ PHP-এর magic method গুলি (__get, __set, __isset, এবং __unset) Magic Methods ব্যবহার করে একটি বৈশিষ্ট্য সমৃদ্ধ উদাহরণ তৈরি করা যাক। এই উদাহরণে, আমরা একটি ডাটাবেসে ব্যবহারকারীর রেকর্ড পরিচালনার জন্য একটি সাধারণ ORM-এর মতো (Object-Relational Mapping) সিস্টেম তৈরি করব।

<?php 
class User
{
    private $data = [];
    private $db;

    public function __construct(PDO $db)
    {
        $this->db = $db;
    }

    public function __set($name, $value)
    {
        $this->data[$name] = $value;
    }

    public function __get($name)
    {
        return $this->data[$name] ?? null;
    }

    public function __isset($name)
    {
        return isset($this->data[$name]);
    }

    public function __unset($name)
    {
        unset($this->data[$name]);
    }

    public function save()
    {
        // If the user has an ID, update the existing record; otherwise, insert a new record.
        if ($this->__isset('id')) {
            $this->update();
        } else {
            $this->insert();
        }
    }

    private function insert()
    {
        // Insert the user data into the database.
        $columns = implode(', ', array_keys($this->data));
        $values = ':' . implode(', :', array_keys($this->data));

        $query = "INSERT INTO users ($columns) VALUES ($values)";
        $statement = $this->db->prepare($query);

        foreach ($this->data as $key => $value) {
            $statement->bindValue(":$key", $value);
        }

        $statement->execute();
    }

    private function update()
    {
        // Update the user data in the database.
        $updates = [];
        foreach ($this->data as $key => $value) {
            $updates[] = "$key = :$key";
        }

        $query = "UPDATE users SET " . implode(', ', $updates) . " WHERE id = :id";
        $statement = $this->db->prepare($query);

        foreach ($this->data as $key => $value) {
            $statement->bindValue(":$key", $value);
        }

        $statement->execute();
    }

    public function load($userId)
    {
        // Load user data from the database based on the provided user ID.
        $query = "SELECT * FROM users WHERE id = :id";
        $statement = $this->db->prepare($query);
        $statement->bindParam(':id', $userId, PDO::PARAM_INT);
        $statement->execute();

        $userData = $statement->fetch(PDO::FETCH_ASSOC);

        if ($userData) {
            $this->data = $userData;
        }
    }
}

// Example Usage:
try {
    $pdo = new PDO('mysql:host=localhost;dbname=eshop', 'root', '');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $user = new User($pdo);

    // Creating a new user
    $user->name = 'John Doe';
    $user->email = 'john@example.com';
    $user->save();

    // Loading an existing user
    $loadedUserId = 1; // Assuming user with ID 1 exists
    $user->load($loadedUserId);

    // Updating user data
    $user->name = 'Updated Name';
    $user->save();

    // Deleting a property
    unset($user->email);
    $user->save();

} catch (PDOException $e) {
    echo "Connection failed: " . $e->getMessage();
}

এই উদাহরণে:

  • User class এ property access পরিচালনা করার জন্য magic methods ব্যবহার করে, এটি ইউজারের ডেটার সাথে কাজ করা সুবিধাজনক করে তোলে।
  • save method নির্ধারণ করে যে id property উপস্থিতির উপর ভিত্তি করে একটি নতুন রেকর্ড সন্নিবেশ করানো বা বিদ্যমান একটি আপডেট করা হবে কিনা
  • insert এবং update method গুলি ডাটাবেসে রেকর্ড সন্নিবেশ এবং আপডেট করার জন্য SQL queries গুলি পরিচালনা করে।
  • load method প্রদত্ত user ID এর উপর ভিত্তি করে ইউজারের ডেটা নিয়ে আসে।
  • উদাহরণের ব্যবহার আরও পঠনযোগ্য সিনট্যাক্স সহ একটি user object এর creating, loading, updating এবং deleting করে ফেলার প্রদর্শন করে।

এই ধরনের পন্থা ডাটাবেস রেকর্ডগুলির সাথে ইন্টারঅ্যাক্ট করার একটি clean এবং অভিব্যক্তিপূর্ণ উপায়ের সুযোগ দেয়, কোডটিকে আরও রক্ষণাবেক্ষণযোগ্য এবং পাঠযোগ্য করে তোলে। এটি লক্ষ্য করা গুরুত্বপূর্ণ যে এই উদাহরণটি একটি simplified illustration, এবং একটি সম্পূর্ণ ORM লাইব্রেরি জটিল অ্যাপ্লিকেশনের জন্য আরও উপযুক্ত হতে পারে।

আমি মাসুদ আলম, বাংলাদেশের ৩৬ তম Zend Certified Engineer । ২০০৯ সালে কম্পিউটার সাইন্স থেকে বেচেলর ডিগ্রী অর্জন করি। দীর্ঘ ১৫ বছর আমি Winux Soft, SSL Wireless, IBCS-PRIMAX, Max Group, Canadian International Development Agency (CIDA), Care Bangladesh, World Vision, Hellen Keller, Amarbebsha Ltd সহ বিভিন্ন দেশি বিদেশী কোম্পানিতে ডেটা সাইন্স, মেশিন লার্নিং, বিগ ডেটা, ওয়েব ডেভেলপমেন্ট এবং সফটওয়্যার ডেভেলপমেন্ট এর উপর বিভিন্ন লিডিং পজিশন এ চাকরি এবং প্রজেক্ট লিড করি। এছাড়াও বাংলাদেশের ১৮৫ জন জেন্ড সার্টিফাইড ইঞ্জিনিয়ার এর মধ্যে ১২০ এরও অধিক ছাত্র আমার হাতে জেন্ড সার্টিফাইড ইঞ্জিনিয়ার হয়েছেন। বর্তমানে w3programmers ট্রেনিং ইনস্টিটিউট এ PHP এর উপর Professional এবং Advance Zend Certified PHP -8.2 Engineering, Laravel Mastering Course with ReactJS, Python Beginning To Advance with Blockchain, Machine Learning and Data Science, Professional WordPress Plugin Development Beginning to Advance কোর্স করাই। আর অবসর সময়ে w3programmers.com এ ওয়েব টেকনোলজি নিয়ে লেখালেখি করি।

Leave a Reply