معین پس از مدتها حضور در کوئرا، تصمیم گرفت برای ایجاد تنوع، در مصاحبه استخدامی جونیورا شرکت کند؛ در این مصاحبه از او خواستهاند که پروژهای با استفاده از ORM مخصوص Laravel انجام دهد؛ اما او معتقد است هرکسی میتواند با کمی آموزش، رانندگی را یاد بگیرد؛ ولی فقط یک مکانیک دقیقاً میداند ماشین چگونه کار میکند. بنابراین، تصمیم گرفت با هدف نشاندادن تبحر واقعی خود، بهجای استفاده از ORM آماده، یک ORM اختصاصی به زبان PHP بنویسد و آن را به افتخار خودش معین سخنور (Moein Eloquent) نامگذاری کند. مشکل اینجاست که پروژه اصلی بهاندازه کافی وقتگیر است و معین برای پیادهسازی ORM اختصاصیاش به زمان بیشتری نیاز دارد؛ از همین رو، او از شما کمک میخواهد تا در تکمیل این ORM همراهش باشید.
جزئیات پروژه
پروژه اولیه را از این لینک دانلود کنید.
ساختار فایلها
moein-eloquent
├── Database.php
├── > QueryBuilder.php <
├── > Model.php <
├── User.php
├── autoload.php
└── test
└── EloquentSampleTest.php
راهاندازی پروژه
برای راهاندازی پروژه، به پایگاهداده 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
وقتی مراحل نصب کامل شد، دستور زیر را در ترمینال وارد کنید تا با سرویس MySQL در کانتینر ساختهشده ارتباط برقرار کنید:
docker exec -it mysql-container mysql -u root
حال دستور زیر را در محیط تعاملی MySQL وارد کنید تا پایگاهداده testdb
استفاده شود:
USE testdb;
اکنون میتوانیم جدول 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
);
این پروژه شامل ۵ فایل است که ۳ فایل از قبل تکمیل شدهاند. شما باید فایلهای Model.php
و QueryBuilder.php
را پیادهسازی کنید؛ در ادامه، جزئیات هرکدام از فایلها و متدهای آنها را میتوانید مشاهده کنید:
فایل Database.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;
}
}
?>
فایل QueryBuilder.php
QueryBuilder.php
کلاس QueryBuilder
برای ساخت و اجرای کوئریهای پایگاهداده طراحی شده است. این کلاس امکاناتی مانند SELECT
، WHERE
، GROUP BY
، HAVING
، ORDER BY
، LIMIT
و عملیات CRUD را فراهم میکند. در پیادهسازی متدهای این کلاس، از دیزاین پترن Builder استفاده کنید.
متد select()
این متد تعیین میکند که کدام ستونها در نتایج select()
SELECT
آورده شوند.
public function select(...$columns): self
{
// TODO
}
مثال
User::select('name', 'age')->get();
User::select(['name', 'age'])->get();
User::select('*')->get();
User::select('age', 'COUNT(*) AS count')->groupBy('age')->get();
متد where()
این متد برای افزودن شرطها به کوئریهای where()
WHERE
استفاده میشود.
- امکان استفاده از عملگرهای مقایسهای (
=
,>
,<
,LIKE
، و ...) وجود دارد. - در صورتی که فقط دو آرگومان به متد داده شود، عملگر مقایسهای پیشفرض (
=
) در نظر گرفته میشود.
public function where(string $column, mixed $operator = null, mixed $value = null): self
{
// TODO
}
مثال
User::where('age', '>=', '32')->get();
User::where('age', '=', '40')->get();
User::where('age', '22')->get();
User::where('name', 'LIKE', 'M%')->get();
متد groupBy()
این متد کوئری را براساس یک یا چند ستون گروهبندی (groupBy()
GROUP BY
) میکند.
public function groupBy(...$columns): self
{
// TODO
}
مثال
User::select('age', 'COUNT(*) AS count')->groupBy('age')->get();
User::select('age', 'email', 'COUNT(*) AS count')->groupBy('age', 'email')->get();
متد having()
این متد برای فیلترکردن نتایج پس از having()
GROUP BY
استفاده میشود.
public function having(string $column, string $operator, mixed $value): self
{
// TODO
}
مثال
User::select('age', 'COUNT(*) AS count')->groupBy('age')->having('age', '>', 32)->get();
متد orderBy()
این متد چیدمان نتایج را براساس یک ستون و ترتیب orderBy()
ASC
یا DESC
تعیین میکند.
public function orderBy(string $column, string $direction = 'ASC'): self
{
// TODO
}
مثال
User::orderBy('age')->get();
User::orderBy('age')->orderBy('name', 'DESC')->get();
متد limit()
این متد تعداد نتایج بازگشتی را محدود (limit()
LIMIT
) میکند.
public function limit(int $limit): self
{
// TODO
}
مثال
User::orderBy('age')->limit(1)->get();
متد offset
این متد تعدادی از نتایج بازگشتی را رد (skip) میکند.
offset
public function offset(int $offset): self
{
// TODO
}
مثال
User::orderBy('age')->offset(1)->limit(1)->get();
متد get()
این متد کوئری نهایی را اجرا کرده و نتایج را بهصورت آرایهای از اشیا مدل برمیگرداند.
get()
public function get(): array
{
// TODO
}
متد first()
این متد اولین نتیجهی کوئری را دریافت کرده و مقدار first()
null
را در صورت خالیبودن بازمیگرداند.
public function first()
{
// TODO
}
مثال
User::orderBy('age', 'DESC')->first();
متد count()
این متد تعداد کل ردیفهای خروجی کوئری را برمیگرداند (count()
COUNT(*)
).
public function count(): int
{
// TODO
}
مثال
User::count();
User::where('age', 24)->count()
متدهای sum()
, avg()
, min()
, max()
این متدها مقدار مجموع، میانگین، حداقل و حداکثر یک ستون را محاسبه میکنند.
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
}
مثال
User::sum('age');
User::avg('age');
User::min('age');
User::max('age');
متد insert()
این متد یک رکورد جدید در پایگاهداده درج (insert()
INSERT
) کرده و id
آن را برمیگرداند.
public function insert(array $data): int
{
// TODO
}
مثال
User::insert(
[
'id' => 10,
'name' => 'New User',
'age' => 50,
'email' => 'newuser@email.com'
]
)
متد update()
این متد دادههای جدول را بر اساس update()
WHERE
بهروزرسانی میکند.
- اگر شرط
WHERE
تعیین نشده باشد، خطا (Exception
) ایجاد میشود.
public function update(array $data): bool
{
// TODO
}
مثال
User::where('id', 10)->update(['name' => 'Newer Name']);
متد delete()
این متد رکوردهای پایگاهداده را حذف (delete()
DELETE
) میکند.
- اگر
WHERE
تعیین نشده باشد، عملیات متوقف شده و خطا (Exception
) صادر میشود.
public function delete(): bool
{
// TODO
}
مثال
User::where('id', 10)->delete();
فایل Model.php
این فایل باید پیادهسازی شود. در این فایل یک کلاس انتزاعی (abstract) بهنام Model.php
Model
وجود دارد که مدلهای پایگاهداده از آن ارثبری میکنند. این کلاس دو پراپرتی دارد؛ نام جدول و آرایه attributes
. نام جدول باید توسط مدلهایی که از این کلاس ارثبری میکنند Overwrite شود. آرایه attributes
برای ستکردن پراپرتیهای dynamic است. این کلاس دارای چند متد است که باید پیادهسازی شوند:
متد query()
این متد یک شیء از کلاس query()
QueryBuilder
ایجاد کرده و برای اجرای کوئریهای پایگاهداده مورد استفاده قرار میگیرد.
public static function query(): QueryBuilder
{
// TODO
}
متد allQuery()
این متد تمام رکوردهای موجود در جدول مرتبط با مدل را دریافت کرده و بهصورت آرایهای از اشیا برمیگرداند.
allQuery()
public static function allQuery(): array
{
// TODO
}
مثال
User::all();
متد findQuery()
این متد یک رکورد خاص را بر اساس مقدار findQuery()
id
جستوجو میکند.
- اگر رکوردی یافت شود، نمونهای از مدل را برمیگرداند.
- در غیر این صورت مقدار
null
برمیگردد.
public static function findQuery(int $id): ?self
{
// TODO
}
مثال
User::find(1);
متد createQuery()
این متد یک رکورد جدید در جدول مرتبط با مدل ایجاد میکند.
createQuery()
- دادههای جدید را در پایگاهداده درج کرده و نمونهای از مدل را که شامل این دادهها است، برمیگرداند.
public static function createQuery(array $data): ?self
{
// TODO
}
مثال
User::create([
'id' => 20,
'name' => 'Jason Voorhees',
'email' => 'jason@gmail.com',
'age' => 60
]);
متد firstQuery()
این متد اولین رکورد موجود در جدول را دریافت کرده و بهعنوان نمونهای از مدل برمیگرداند.
firstQuery()
public static function firstQuery(): ?self
{
// TODO
}
مثال
User::first();
متد hydrateQuery()
این متد یک نمونه از مدل را با دادههای ورودی مقداردهی میکند.
hydrateQuery()
- برای پر کردن شیء مدل از یک آرایه داده استفاده میشود.
public static function hydrateQuery(array $data): self
{
// TODO
}
متد updateQuery()
این متد مقدار فیلدهای یک رکورد را بهروزرسانی میکند.
updateQuery()
- رکورد مورد نظر باید در object و پایگاهداده همزمان بهروزرسانی شود.
- اگر بهروزرسانی موفقیتآمیز باشد، مقدار
true
برمیگردد. - در غیر این صورت مقدار
false
بازمیگردد.
public function updateQuery(array $data): bool
{
// TODO
}
مثال
$user = User::find(1);
$user->update(['age' => 30]);
متد deleteQuery()
این متد رکورد مربوطه را از پایگاهداده حذف میکند.
deleteQuery()
- اگر حذف موفقیتآمیز باشد، مقدار
true
برمیگردد. - در غیر این صورت مقدار
false
بازمیگردد.
public function deleteQuery(): bool
{
// TODO
}
مثال
$user = User::find(1);
$user->delete();
فایل User.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;
}
?>
فایل autoload.php
این فایل صرفا شامل یک رجیستر است که کلاسهای مورد نیاز را از فایلهای دیگر فراخوانی میکند. شما نیازی به پیادهسازی این فایل ندارید.
autoload.php
<?php
spl_autoload_register(function ($class_name) {
require __DIR__ . "/$class_name.php";
});
?>
نکات
- برای حل این سوال باید از دیزاین پترن Builder استفاده کنید.
- دقت کنید که صرفا تغییراتی که در فایلهای
QueryBuilder.php
وModel.php
ایجاد میکنید بررسی خواهند شد؛ تغییراتی که در فایلهای دیگر پروژه ایجاد میکنید در سیستم داوری نادیده گرفته خواهند شد.
آنچه باید آپلود کنید
پس از پیادهسازی فایلهای مورد نیاز، فایلهای QueryBuilder.php
و Model.php
را بهصورت فایل Zip ارسال کنید.
ارسال پاسخ برای این سؤال