معین پس از مدت‌ها حضور در کوئرا، تصمیم گرفت برای ایجاد تنوع، در مصاحبه استخدامی جونیورا شرکت کند؛ در این مصاحبه از او خواسته‌اند که پروژه‌ای با استفاده از ORM مخصوص Laravel انجام دهد؛ اما او معتقد است هرکسی می‌تواند با کمی آموزش، رانندگی را یاد بگیرد؛ ولی فقط یک مکانیک دقیقاً می‌داند ماشین چگونه کار می‌کند. بنابراین، تصمیم گرفت با هدف نشان‌دادن تبحر واقعی خود، به‌جای استفاده از ORM آماده، یک ORM اختصاصی به زبان PHP بنویسد و آن را به افتخار خودش معین سخنور (Moein Eloquent) نام‌گذاری کند. مشکل اینجاست که پروژه اصلی به‌اندازه کافی وقت‌گیر است و معین برای پیاده‌سازی ORM اختصاصی‌اش به زمان بیشتری نیاز دارد؛ از همین رو، او از شما کمک می‌خواهد تا در تکمیل این ORM همراهش باشید.

معین سخنور

جزئیات پروژه

پروژه اولیه را از این لینک دانلود کنید.

ساختار فایل‌ها

moein-eloquent
├── Database.php
├── > QueryBuilder.php <
├── > Model.php <
├── User.php
├── autoload.php
└── test
    └── EloquentSampleTest.php
Plain text

راه‌اندازی پروژه

برای راه‌اندازی پروژه، به پایگاه‌داده MySQL نیاز است. در این بخش، به راه‌اندازی MySQL در داکر می‌پردازیم. اگر تاکنون داکر را نصب نکرده‌اید، به‌کمک این لینک آن را نصب کنید.

برای نصب و راه‌اندازی کانتینر MySQL به‌کمک داکر، دستور زیر را در ترمینال وارد کنید تا ایمیج MySQL دانلود شود و کانتینر راه‌اندازی شود:

docker run --name mysql-container \
    -e MYSQL_ALLOW_EMPTY_PASSWORD=yes \
    -e MYSQL_DATABASE=testdb \
    -p 3306:3306 \
    -d mysql:latest
Bash
terminal

وقتی مراحل نصب کامل شد، دستور زیر را در ترمینال وارد کنید تا با سرویس MySQL در کانتینر ساخته‌شده ارتباط برقرار کنید:

docker exec -it mysql-container mysql -u root
Bash
terminal

حال دستور زیر را در محیط تعاملی MySQL وارد کنید تا پایگاه‌داده testdb استفاده شود:

USE testdb;
SQL
sql

اکنون می‌توانیم جدول users را ایجاد کنیم؛ دستور زیر را وارد کنید تا جدول users در پایگاه‌داده testdb ساخته شود:

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    age INT NOT NULL
);
SQL
sql

این پروژه شامل ۵ فایل است که ۳ فایل از قبل تکمیل شده‌اند. شما باید فایل‌های Model.php و QueryBuilder.php را پیاده‌سازی کنید؛ در ادامه، جزئیات هرکدام از فایل‌ها و متد‌های آن‌ها را می‌توانید مشاهده کنید:

فایل Database.php

این فایل شامل یک کلاس به‌نام Database است که یک شی pdo را مقداردهی اولیه می‌کند و می‌توان با استفاده از Database::getInstance() به آن دسترسی پیدا کرد. این فایل از قبل پیاده‌سازی شده و شما نیازی به‌ تغییر در آن ندارید؛ در پروژه ارسالی شما، این فایل جایگزین می‌شود، بنابراین هر تغییری در آن ایجاد کنید، نادیده گرفته خواهد شد. اما اگر می‌خواهید نتایج کوئری‌هایتان ببینید، باید مقادیر موجود در این فایل را تغییر دهید تا بتوانید پروژه را روی سیستم خودتان اجرا کنید.

<?php

class Database {
    private static $instance = null;
    private $pdo;
    
    private function __construct() {
        $this->pdo = new PDO("mysql:host=127.0.0.1;dbname=testdb", "root", "");
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }
    
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new Database();
        }
        return self::$instance->pdo;
    }
}

?>
PHP
Database.php

فایل QueryBuilder.php

کلاس QueryBuilder برای ساخت و اجرای کوئری‌های پایگاه‌داده طراحی شده است. این کلاس امکاناتی مانند SELECT، WHERE، GROUP BY، HAVING، ORDER BY، LIMIT و عملیات CRUD را فراهم می‌کند. در پیاده‌سازی متدهای این کلاس، از دیزاین پترن Builder استفاده کنید.

متد select()

این متد تعیین می‌کند که کدام ستون‌ها در نتایج SELECT آورده شوند.

public function select(...$columns): self 
{
    // TODO
}
PHP

مثال

User::select('name', 'age')->get();
User::select(['name', 'age'])->get();
User::select('*')->get();
User::select('age', 'COUNT(*) AS count')->groupBy('age')->get();
PHP

متد where()

این متد برای افزودن شرط‌ها به کوئری‌های WHERE استفاده می‌شود.

  • امکان استفاده از عملگرهای مقایسه‌ای (=, >, <, LIKE، و ...) وجود دارد.
  • در صورتی که فقط دو آرگومان به متد داده شود، عملگر مقایسه‌ای پیش‌فرض (=) در نظر گرفته می‌شود.
public function where(string $column, mixed $operator = null, mixed $value = null): self 
{
    // TODO
}
PHP

مثال

User::where('age', '>=', '32')->get();
User::where('age', '=', '40')->get();
User::where('age', '22')->get();
User::where('name', 'LIKE', 'M%')->get();
PHP

متد groupBy()

این متد کوئری را براساس یک یا چند ستون گروه‌بندی (GROUP BY) می‌کند.

public function groupBy(...$columns): self 
{
    // TODO
}
PHP

مثال

User::select('age', 'COUNT(*) AS count')->groupBy('age')->get();
User::select('age', 'email', 'COUNT(*) AS count')->groupBy('age', 'email')->get();
PHP

متد having()

این متد برای فیلترکردن نتایج پس از GROUP BY استفاده می‌شود.

public function having(string $column, string $operator, mixed $value): self 
{
    // TODO
}
PHP

مثال

User::select('age', 'COUNT(*) AS count')->groupBy('age')->having('age', '>', 32)->get();
PHP

متد orderBy()

این متد چیدمان نتایج را براساس یک ستون و ترتیب ASC یا DESC تعیین می‌کند.

public function orderBy(string $column, string $direction = 'ASC'): self 
{
    // TODO
}
PHP

مثال

User::orderBy('age')->get();
User::orderBy('age')->orderBy('name', 'DESC')->get();
PHP

متد limit()

این متد تعداد نتایج بازگشتی را محدود (LIMIT) می‌کند.

public function limit(int $limit): self 
{
    // TODO
}
PHP

مثال

User::orderBy('age')->limit(1)->get();
PHP

متد offset

این متد تعدادی از نتایج بازگشتی را رد (skip) می‌کند.

public function offset(int $offset): self
{
    // TODO
}
PHP

مثال

User::orderBy('age')->offset(1)->limit(1)->get();
PHP

متد get()

این متد کوئری نهایی را اجرا کرده و نتایج را به‌صورت آرایه‌ای از اشیا مدل برمی‌گرداند.

public function get(): array 
{
    // TODO
}
PHP

متد first()

این متد اولین نتیجه‌ی کوئری را دریافت کرده و مقدار null را در صورت خالی‌بودن بازمی‌گرداند.

public function first() 
{
    // TODO
}
PHP

مثال

User::orderBy('age', 'DESC')->first();
PHP

متد count()

این متد تعداد کل ردیف‌های خروجی کوئری را برمی‌گرداند (COUNT(*)).

public function count(): int 
{
    // TODO
}
PHP

مثال

User::count();
User::where('age', 24)->count()
PHP

متدهای sum(), avg(), min(), max()

این متدها مقدار مجموع، میانگین، حداقل و حداکثر یک ستون را محاسبه می‌کنند.

private function aggregate($function, $column) 
{
    // TODO
}

public function sum($column) 
{
    // TODO
}

public function avg($column) 
{
    // TODO
}

public function min($column) 
{
    // TODO
}

public function max($column) 
{
    // TODO
}
PHP

مثال

User::sum('age');
User::avg('age');
User::min('age');
User::max('age');
PHP

متد insert()

این متد یک رکورد جدید در پایگاه‌داده درج (INSERT) کرده و id آن را برمی‌گرداند.

public function insert(array $data): int 
{
    // TODO
}
PHP

مثال

User::insert(
    [
        'id' => 10, 
        'name' => 'New User', 
        'age' => 50, 
        'email' => 'newuser@email.com'
    ]
)
PHP

متد update()

این متد داده‌های جدول را بر اساس WHERE به‌روزرسانی می‌کند.

  • اگر شرط WHERE تعیین نشده باشد، خطا (Exception) ایجاد می‌شود.
public function update(array $data): bool 
{
    // TODO
}
PHP

مثال

User::where('id', 10)->update(['name' => 'Newer Name']);
PHP

متد delete()

این متد رکوردهای پایگاه‌داده را حذف (DELETE) می‌کند.

  • اگر WHERE تعیین نشده باشد، عملیات متوقف شده و خطا (Exception) صادر می‌شود.
public function delete(): bool 
{
    // TODO
}
PHP

مثال

User::where('id', 10)->delete();
PHP

فایل Model.php

این فایل باید پیاده‌سازی شود. در این فایل یک کلاس انتزاعی (abstract) به‌نام Model وجود دارد که مدل‌های پایگاه‌داده از آن ارث‌بری می‌کنند. این کلاس دو پراپرتی دارد؛ نام جدول و آرایه attributes. نام جدول باید توسط مدل‌هایی که از این کلاس ارث‌بری می‌کنند Overwrite شود. آرایه attributes برای ست‌کردن پراپرتی‌های dynamic است. این کلاس دارای چند متد است که باید پیاده‌سازی شوند:

متد query()

این متد یک شیء از کلاس QueryBuilder ایجاد کرده و برای اجرای کوئری‌های پایگاه‌داده مورد استفاده قرار می‌گیرد.

public static function query(): QueryBuilder
{
    // TODO
}
PHP

متد allQuery()

این متد تمام رکوردهای موجود در جدول مرتبط با مدل را دریافت کرده و به‌صورت آرایه‌ای از اشیا برمی‌گرداند.

public static function allQuery(): array
{
    // TODO
}
PHP

مثال

User::all();
PHP

متد findQuery()

این متد یک رکورد خاص را بر اساس مقدار id جست‌وجو می‌کند.

  • اگر رکوردی یافت شود، نمونه‌ای از مدل را برمی‌گرداند.
  • در غیر این صورت مقدار null برمی‌گردد.
public static function findQuery(int $id): ?self
{
    // TODO
}
PHP

مثال

User::find(1);
PHP

متد createQuery()

این متد یک رکورد جدید در جدول مرتبط با مدل ایجاد می‌کند.

  • داده‌های جدید را در پایگاه‌داده درج کرده و نمونه‌ای از مدل را که شامل این داده‌ها است، برمی‌گرداند.
public static function createQuery(array $data): ?self
{
    // TODO
}
PHP

مثال

User::create([
    'id' => 20,
    'name' => 'Jason Voorhees',
    'email' => 'jason@gmail.com',
    'age' => 60
]);
PHP

متد firstQuery()

این متد اولین رکورد موجود در جدول را دریافت کرده و به‌عنوان نمونه‌ای از مدل برمی‌گرداند.

public static function firstQuery(): ?self
{
    // TODO
}
PHP

مثال

User::first();
PHP

متد hydrateQuery()

این متد یک نمونه از مدل را با داده‌های ورودی مقداردهی می‌کند.

  • برای پر کردن شیء مدل از یک آرایه داده استفاده می‌شود.
public static function hydrateQuery(array $data): self
{
    // TODO
}
PHP

متد updateQuery()

این متد مقدار فیلدهای یک رکورد را به‌روزرسانی می‌کند.

  • رکورد مورد نظر باید در object و پایگاه‌داده همزمان به‌روزرسانی شود.
  • اگر به‌روزرسانی موفقیت‌آمیز باشد، مقدار true برمی‌گردد.
  • در غیر این صورت مقدار false بازمی‌گردد.
public function updateQuery(array $data): bool
{
    // TODO
}
PHP

مثال

$user = User::find(1);
$user->update(['age' => 30]);
PHP

متد deleteQuery()

این متد رکورد مربوطه را از پایگاه‌داده حذف می‌کند.

  • اگر حذف موفقیت‌آمیز باشد، مقدار true برمی‌گردد.
  • در غیر این صورت مقدار false بازمی‌گردد.
public function deleteQuery(): bool
{
    // TODO
}
PHP

مثال

$user = User::find(1);
$user->delete();
PHP

فایل User.php

این فایل شامل یک کلاس User است که از کلاس Model ارث‌بری می‌کند. در این کلاس، صرفا مقادیر مورد نیاز تعریف یا مقداردهی شده‌اند. شما نیازی به پیاده‌سازی این فایل ندارید.

<?php

require_once 'autoload.php';

class User extends Model {
    protected static string $table = 'users';

    public int $id;
    public string $name;
    public string $email;
    public int $age;
}

?>
PHP
User.php

فایل autoload.php

این فایل صرفا شامل یک رجیستر است که کلاس‌های مورد نیاز را از فایل‌های دیگر فراخوانی می‌کند. شما نیازی به پیاده‌سازی این فایل ندارید.

<?php

spl_autoload_register(function ($class_name) {
    require __DIR__ . "/$class_name.php";
});

?>
PHP
autoload.php

نکات

  • برای حل این سوال باید از دیزاین پترن Builder استفاده کنید.
  • دقت کنید که صرفا تغییراتی که در فایل‌های QueryBuilder.php و Model.php ایجاد می‌کنید بررسی خواهند شد؛ تغییراتی که در فایل‌های دیگر پروژه ایجاد می‌کنید در سیستم داوری نادیده گرفته خواهند شد.

آن‌چه باید آپلود کنید

پس از پیاده‌سازی فایل‌های مورد نیاز، فایل‌های QueryBuilder.php و Model.php را به‌صورت فایل Zip ارسال کنید.


ارسال پاسخ برای این سؤال
فایلی انتخاب نشده است.