میلاد بهتازگی در دیجیکالا بهعنوان توسعهدهندهی *junior* استخدام شده است. اولین *task* ای که به او واگذار شده، بازنویسی بخش *pagination* وبسایت دیجیاستایل است. از آنجا که میلاد تجربهی انجام این کار را ندارد، از شما میخواهیم تا این بخش را برای او پیادهسازی کنید.
## ساختار پروژه
پروژهی اوّلیه را از [اینجا](https://quera.ir/qbox/download/CxDyLpRy1R/pagination-initial.zip) دانلود کنید. ساختار پروژه بهصورت زیر است:
1. فایل `pagination.tpl`: این فایل شامل قالب *HTML* بخش *pagination* بوده و محتوای آن بهصورت زیر است:
```html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="assets/css/style.css">
<title>Pagination</title>
</head>
<body>
<main class="container">
<ul class="pagination justify-content-center mt-3">
{{ @pages }}
</ul>
</main>
</body>
</html>
```
2. فایل `pagination.php`: شامل دو تابع زیر است:
+ `renderPagination`: این تابع بهترتیب یک رشته حاوی قالب *HTML* ، تعداد مطالب موجود در وبسایت، حداکثر تعداد مطالب در هر صفحه، شمارهی صفحهی فعلی و لینک پایهی صفحات را دریافت میکند. در نهایت، بخش *pagination* را بهصورت یک رشته برمیگرداند.
+ `getPaginationButtons`: این تابع بهترتیب تعداد مطالب موجود در وبسایت، حداکثر تعداد مطالب در هر صفحه و شمارهی صفحهی فعلی را دریافت کرده و آرایهای شامل دکمهها جهت نمایش در بخش *pagination* را برمیگرداند. هر دکمه یک آرایهی *associative* است که شامل دو کلید `text` و `number` است. مقدار کلید `text` برای دکمهی رفتن به صفحهی قبلی `prev` و برای دکمهی رفتن به صفحهی بعدی `next` است. همچنین، مقدار کلید `text` برای دکمهی `...` همان `...` است. مقدار کلید `number` بیانگر شمارهی صفحهی موجود در لینک دکمه است؛ برای مثال اگر ۴ صفحه داشته باشیم و در حال حاضر در صفحهی ۳ باشیم، مقدار کلید `number` برای دکمههای `prev` و `next` بهترتیب برابر با `2` و `4` خواهد بود. بدیهی است که مقدار کلید `number` برای سایر دکمهها (بهجز دکمهی `...`) باید برابر با مقدار کلید `text` باشد.
**توجه:** دکمهی `...` نباید کلید `number` را داشته باشد.
محتویات فایل `pagination.php` بهصورت زیر است:
```php
<?php
function getPaginationButtons($total_items, $per_page, $current_page)
{
// Implement getPaginationButtons function here
}
function renderPagination($pagination_template, $total_items, $per_page, $current_page, $base_url)
{
$pages = getPaginationButtons($total_items, $per_page, $current_page);
$html = '';
foreach ($pages as $page) {
$page['text'] = str_replace(['prev', 'next'], ['«', '»'], $page['text']);
if (in_array($page['text'], ['«', '...', '»'])) {
$html .= '<li class="page-item">
<a class="page-link" href="' . (isset($page['number']) ? $base_url . $page['number'] : '#') . '">
<span aria-hidden="true">' . $page['text'] . '</span>
</a>
</li>';
} else {
$html .= '<li class="page-item' . ($page['number'] == $current_page ? ' active' : '')
. '"><a class="page-link" href="' . $base_url . $page['number'] . '">'
. $page['number'] . '</a></li>';
}
}
return str_replace('{{ @pages }}', $html, $pagination_template);
}
```
قوانین صفحهبندی بهشرح زیر هستند:
+ صفحات اول و آخر باید از طریق همهی صفحات در دسترس باشند.
+ دو صفحهی قبلی و دو صفحهی بعدی هر صفحه باید در دسترس باشند.
+ صفحاتی که فاصلهشان از صفحهی فعلی بزرگتر یا مساوی ۳ است با یک دکمهی `...` نمایش داده میشوند.
+ اگر فاصلهی دو صفحه از یکدیگر ۲ واحد باشد، به جای دکمهی `...` دکمهی شمارهی صفحهی بین آنها قرار میگیرد؛ برای مثال، بین دکمههای ۵ و ۷ هیچگاه دکمهی `...` نمیآید.
+ دکمهی `prev` باید در صورتی نمایش داده شود که شمارهی صفحهی فعلی ۱ نباشد.
+ دکمهی `next` باید در صورتی نمایش داده شود که شمارهی صفحهی فعلی برابر با تعداد صفحات نباشد.
**توجه:** ترتیب آرایههای خروجی تابع `getPaginationButtons` باید بهصورت زیر باشد:
+ دکمهی `prev` (در صورت وجود)
+ دکمهی شمارهی صفحات با ترتیب صعودی (میتواند شامل دکمهی `...` نیز باشد.)
+ دکمهی `next` (در صورت وجود)
### مثال ۱:
```php
<?php
require 'pagination.php';
getPaginationButtons(56, 5, 7);
/**
[
["text" => "prev", "number" => 6],
["text" => "1", "number" => 1],
["text" => "..."],
["text" => "5", "number" => 5],
["text" => "6", "number" => 6],
["text" => "7", "number" => 7],
["text" => "8", "number" => 8],
["text" => "9", "number" => 9],
["text" => "..."],
["text" => "12", "number" => 12],
["text" => "next", "number" => 8]
]
*/
$pagination_template = file_get_contents('pagination.tpl');
echo renderPagination($pagination_template, 56, 5, 7, 'index.php?page=');
```
خروجی مورد انتظار:
![pagination-1](https://quera.ir/qbox/view/hLTJofxmjG/pagination-1.png)
### مثال ۲:
```php
<?php
require 'pagination.php';
getPaginationButtons(14, 4, 1);
/**
[
["text" => "1", "number" => 1],
["text" => "2", "number" => 2],
["text" => "3", "number" => 3],
["text" => "4", "number" => 4],
["text" => "next", "number" => 2]
]
*/
$pagination_template = file_get_contents('pagination.tpl');
echo renderPagination($pagination_template, 14, 4, 1, 'index.php?page=');
```
خروجی مورد انتظار:
![pagination-2](https://quera.ir/qbox/view/ht7Gee4mGq/pagination-2.png)
### مثال ۳:
```php
<?php
require 'pagination.php';
getPaginationButtons(40, 10, 4);
/**
[
["text" => "prev", "number" => 3],
["text" => "1", "number" => 1],
["text" => "2", "number" => 2],
["text" => "3", "number" => 3],
["text" => "4", "number" => 4]
]
*/
$pagination_template = file_get_contents('pagination.tpl');
echo renderPagination($pagination_template, 40, 10, 4, 'index.php?page=');
```
خروجی مورد انتظار:
![pagination-3](https://quera.ir/qbox/view/w8iUmEoyVe/pagination-3.png)
### مثال ۴:
```php
<?php
require 'pagination.php';
getPaginationButtons(24, 3, 6);
/**
[
["text" => "prev", "number" => 5],
["text" => "1", "number" => 1],
["text" => "..."],
["text" => "4", "number" => 4],
["text" => "5", "number" => 5],
["text" => "6", "number" => 6],
["text" => "7", "number" => 7],
["text" => "8", "number" => 8],
["text" => "next", "number" => 7]
]
*/
$pagination_template = file_get_contents('pagination.tpl');
echo renderPagination($pagination_template, 24, 3, 6, 'index.php?page=');
```
خروجی مورد انتظار:
![pagination-4](https://quera.ir/qbox/view/69h0FIGDsr/pagination-4.png.png)
### مثال ۵:
```php
<?php
require 'pagination.php';
getPaginationButtons(7, 1, 2);
/**
[
["text" => "prev", "number" => 1],
["text" => "1", "number" => 1],
["text" => "2", "number" => 2],
["text" => "3", "number" => 3],
["text" => "4", "number" => 4],
["text" => "..."],
["text" => "7", "number" => 7],
["text" => "next", "number" => 3]
]
*/
$pagination_template = file_get_contents('pagination.tpl');
echo renderPagination($pagination_template, 7, 1, 2, 'index.php?page=');
```
خروجی مورد انتظار:
![pagination-5](https://quera.ir/qbox/view/Qk9xkWIjqR/pagination-5.png)
3. فایل `index.php`: این فایل توابع موجود در فایل `pagination.php` را فراخوانی کرده و از طریق آن میتوان خروجی بخش *pagination* را مشاهده کرد. محتوای آن بهصورت زیر است:
```php
<?php
require 'pagination.php';
$pagination_template = file_get_contents('pagination.tpl');
echo renderPagination($pagination_template, 7 * 5, 5, 2, 'index.php?page=');
```
## آنچه باید آپلود کنید
پس از پیادهسازی تابع `getPaginationButtons`، فایل `pagination.php` را آپلود کنید.
---
<details class="blue">
<summary>راهنمایی ۱</summary>
تعداد صفحات برابر خواهد بود با $\lceil\frac{\$total\_items}{\$per\_page}\rceil$:
```php
$pages_count = ceil($total_items/ $per_page);
```
</details>
<details class="blue">
<summary>راهنمایی ۲</summary>
آرایهای برای ذخیرهسازی دکمهها در نظر میگیریم:
```php
$buttons = [];
```
اگر تعداد صفحات بیشتر از ۱ باشد، دکمهی `prev` شمارهی `$current_page - 1` را اضافه میکنیم:
```php
if ($current_page > 1) {
$buttons[] = ['text' => 'prev', 'number' => $current_page - 1];
}
```
پس از اضافه کردن دکمههای شماره و `...` (در صورت نیاز)، اگر تعداد صفحات بیشتر از شمارهی صفحهی فعلی باشد، دکمهی `next` با شمارهی `$current_page + 1` را اضافه میکنیم:
```php
if ($current_page < $pages_count) {
$buttons[] = ['text' => 'next', 'number' => $current_page + 1];
}
```
</details>
<details class="blue">
<summary>راهنمایی ۳</summary>
میدانیم حداکثر دو دکمهی `...` در بین دکمهها موجود خواهند بود که یکی از این دکمهها در بین دکمههای سمت چپ دکمهی صفحهی فعلی و دیگری در بین دکمههای سمت راست دکمهی صفحهی فعلی خواهد بود. دو *flag* برای این دو دکمه در نظر میگیریم که هرگاه این دکمهها درج شوند، *flag* شان `true` میشود.
```php
$leftDots = false;
$rightDots = false;
```
پیمایش را از شمارهی ۱ تا تعداد صفحات انجام میدهیم. اگر مقدار شمارهی فعلی ۱ باشد، یا اختلاف آن با شمارهی صفحهی فعلی کمتر از ۳ باشد، یا مقدار شمارندهی فعلی برابر با تعداد صفحات باشد، دکمهای با مقدار شمارنده درج میکنیم.
در غیر اینصورت، اگر مقدار شمارنده کوچکتر از شمارهی صفحهی فعلی باشد و `$leftDots` برابر با `false` باشد، یک دکمهی `...` درج کرده و `$leftDots` را `true` میکنیم.
در غیر اینصورت، اگر مقدار شمارنده بزرگتر از شمارهی صفحهی فعلی باشد و `$rightDots` برابر با `false` باشد، یک دکمهی `...` درج کرده و `$rightDots` را `true` میکنیم.
کد این بخش بهصورت زیر خواهد بود:
```php
for ($i = 1; $i <= $pages_count; $i++) {
if ($i == 1 || abs($current_page - $i) <= 2 || $i == $pages_count) {
$buttons[] = ['text' => $i, 'number' => $i];
}
elseif ($i < $current_page && !$rightDots) {
$buttons[] = ['text' => '...'];
$rightDots = true;
}
elseif ($i > $current_page && !$leftDots) {
$buttons[] = ['text' => '...'];
$leftDots = true;
}
}
```
</details>
مهدی طرفدار پروژههای خاص و سخت است. اخیراً یکی از دوستان صمیمی او، نیما، سفارش نوشتن یک سایت تبلیغاتی را به او داده است. از آنجا که مهدی نمایندگی فروش هاست و سرور ندارد، از نیما درخواست یک هاست برای میزبانی پروژه کرد. نیما هم این درخواست را با کمال میل قبول کرد و دسترسی یک هاست اشتراکی ساده با کمترین امکانات ممکن را برای مهدی فرستاد. مهدی پس از بررسیهای فراوان، متوجه شد که این هاست حتی قابلیت اتصال به _MySQL_ را هم ندارد! لذا تصمیم گرفت که خودش اطلاعات را در قالب _JSON_ درون فایلهای مختلف نگهداری کند و از آنها استفاده کند.
مهدی فرصت کمی برای انجام این پروژه دارد؛ بنابراین از شما میخواهیم که بخش ذخیرهسازی اطلاعات را برای مهدی بنویسید.
# جزئیات پروژه
ساختار فایلهایی که اطلاعات در آنها ذخیره میشوند به صورت زیر است:
```
db_files
├── table1.json
├── table2.json
└── table3.json
```
همهی جداول دیتابیس در یک دایرکتوری مشخص قرار میگیرند. نام هر فایل، نمایانگر نام جدول است. در مثال بالا، سه جدول با نامهای `table1`، `table2` و `table3` وجود دارد.
هر جدول شامل خانهای به نام `schema` است و مشخصات مربوط به ستونهای جدول در آن قرار دارند. در `schema` هر جدول، مقدار پیشفرض ستونها و امکان `null` بودن آنها تعریف میشود. مقدار پیشفرض هر ستون در خانهای به نام `default` و امکان `null` بودن مقدار ستون به صورت `boolean` در خانهای به نام `nullable` ذخیره میشود. لزوماً خانهی `default` برای ستون تعریف نمیشود، امّا خانهی `nullable` برای تمامی ستونها موجود است.
### مثالی از `schema` یک جدول:
```json
{
"first_name": {
"nullable": false
},
"last_name": {
"nullable": true
},
"country": {
"nullable": true,
"default": "Iran"
}
}
```
همچنین هر جدول خانهای به نام `data` دارد که شامل آرایهای از سطرهای موجود در جدول است.
### مثالی از یک جدول:
```json
{
"schema": {
"first_name": {
"nullable": false
},
"last_name": {
"nullable": true
},
"country": {
"nullable": true,
"default": "Iran"
}
},
"data": [{
"first_name": "John",
"last_name": "Smith",
"country": "U.S."
}, {
"first_name": "Ali",
"last_name": null,
"country": "Iran"
}]
}
```
کل پروژه را در قالب کلاسی به نام `JsonDB` با موارد خواستهشدهی زیر پیادهسازی کنید:
+ متد `__construct` را به گونهای پیادهسازی کنید که مسیر اصلی ذخیرهسازی فایلهای جداول را دریافت کند. در صورتی که آدرسی به متد داده نشد، مسیر فعلی اسکریپت بهعنوان مکان ذخیرهسازی فایلهای جداول در نظر گرفته میشود.
+ متد `insert` را به گونهای پیادهسازی کنید که نام جدول و آرایهای از ستونهای سطر موردنظر را دریافت کرده و آن را به انتهای جدول اضافه کند. در صورتی که مقداری برای ستون خاصی تعریف نشود و مقدار پیشفرضی برای آن ستون وجود نداشته باشد و امکان `null` بودن ستون وجود نداشته باشد، باید یک `Exception` با پیام `No value provided for column column_name` (`column_name` نام ستون است) _throw_ شود. در صورتی که مقداری برای ستون خاصی تعریف نشود و مقدار پیشفرضی برای آن ستون وجود نداشته باشد و امکان `null` بودن مقدار ستون وجود داشته باشد، مقدار `null` به عنوان مقدار ستون ذخیره خواهد شد. در صورتی که ستونی به متد داده شود و آن ستون در `schema` جدول موجود نباشد، باید یک `Exception` با پیام `Column column_name not found` (`column_name` نام ستون موردنظر است) _throw_ شود.
### مثالی از فراخوانی متد `insert`:
```php
$db = new JsonDB(__DIR__ . '/db');
$db->insert('users', ['first_name' => 'Ali', 'last_name' => 'AliZadeh', 'country' => 'Iran']);
$db->insert('users', ['first_name' => 'Ali', 'last_name' => 'AliZadeh']);
$db->insert('users', ['last_name' => 'AliZadeh', 'country' => 'Iran']); // Exception: No value provided for column first_name
```
+ متد `select` را به گونهای پیادهسازی کنید که نام جدول و آرایهای از ستونها و مقادیر متناظرشان را دریافت کرده و سطرهایی که مقادیر ستونهایشان با مقادیر ستونهای ورودی یکسان است را در قالب آرایه برگرداند. لزوماً مقادیر همهی ستونها بهعنوان ورودی به متد داده نمیشوند. فرض بر این است که بین شرطها جهت یافتن سطرهای خروجی `AND` وجود دارد. در صورتی که ستونی بهعنوان ورودی به متد داده نشود، باید همهی سطرها بهعنوان خروجی تابع `return` شود. در صورتی که ستونی به متد داده شود و آن ستون در `schema` جدول موجود نباشد، باید یک `Exception` با پیام `Column column_name not found` (`column_name` نام ستون موردنظر است) _throw_ شود.
### مثالی از فراخوانی متد `select`:
```php
$db = new JsonDB(__DIR__ . '/db');
$db->select('users');
/*
[
[
'first_name' => 'Ali',
'last_name' => 'AliZadeh',
'country' => 'Iran'
],
[
'first_name' => 'John',
'last_name' => 'Smith',
'country' => null
]
]
*/
$db->select('users', ['first_name' => 'Ali']);
/*
[
[
'first_name' => 'Ali',
'last_name' => 'AliZadeh',
'country' => 'Iran'
]
]
*/
$db->select('users', ['first_name' => 'Ali', 'country' => null]);
/*
[]
*/
$db->insert('users', ['invalid_column' => 'Sample Value', 'country' => 'Iran']); // Exception: Column invalid_column not found
```
+ متد `update` مقدار یک یا چند ستون را در سطرهایی که مشخص میکنیم تغییر میدهد. این متد شامل سه آرگومان بوده که آرگومان اول نام جدول موردنظر برای بهروزرسانی است، آرگومان دوم شامل مقادیر جدید ستونهایی است که قرار است تغییر کنند و آرگومان سوم مشخص میکند که ستونهای چه سطرهایی باید تغییر کنند؛ به طوری که هر سطری که مقادیر ستونهایش برابر با مقادیر ستونهای آرگومان سوم باشد باید بهروزرسانی شود. لزوماً مقادیر همهی ستونهای جدول در آرگومانهای دوم و سوّم موجود نیستند. اگر آرگومان سوّم به متد داده نشد، باید همهی سطرهای جدول بهروزرسانی شوند. فرض بر این است که بین شرطها جهت یافتن سطرها جهت بهروزرسانی `AND` وجود دارد. در صورتی که ستونی در آرگومان دوم یا سوّم موجود باشد و آن ستون در `schema` جدول موجود نباشد، باید یک `Exception` با پیام `Column column_name not found` (`column_name` نام ستون موردنظر است) _throw_ شود.
### مثالی از فراخوانی متد `update`:
```php
$db = new JsonDB(__DIR__ . '/db');
$db->select('users');
/*
[
[
'first_name' => 'Ali',
'last_name' => 'AliZadeh',
'country' => 'Iran'
],
[
'first_name' => 'John',
'last_name' => 'Smith',
'country' => null
]
]
*/
$db->update('users', ['first_name' => 'Mohammad'], ['first_name' => 'Ali']);
$db->select('users');
/*
[
[
'first_name' => 'Mohammad',
'last_name' => 'AliZadeh',
'country' => 'Iran'
],
[
'first_name' => 'John',
'last_name' => 'Smith',
'country' => null
]
]
*/
$db->update('users', ['first_name' => 'Mohammad']);
$db->select('users');
/*
[
[
'first_name' => 'Mohammad',
'last_name' => 'AliZadeh',
'country' => 'Iran'
],
[
'first_name' => 'Mohammad',
'last_name' => 'Smith',
'country' => null
]
]
*/
$db->update('users', ['invalid_column' => 'Test']); // Exception: Column invalid_column not found
```
+ متد `delete` را به گونهای پیادهسازی کنید که نام جدول و آرایهای از ستونها و مقادیر متناظرشان را دریافت کرده و سطرهایی که مقادیر ستونهایشان با مقادیر ستونهای ورودی یکسان است را از جدول حذف کند. لزوماً مقادیر همهی ستونها بهعنوان ورودی به متد داده نمیشوند. فرض بر این است که بین شرطها جهت یافتن سطرها برای حذف `AND` وجود دارد. در صورتی که ستونی بهعنوان ورودی به متد داده نشود، باید همهی سطرها از جدول حذف شوند. در صورتی که ستونی به متد داده شود و آن ستون در `schema` جدول موجود نباشد، باید یک `Exception` با پیام `Column column_name not found` (`column_name` نام ستون موردنظر است) _throw_ شود.
### مثالی از فراخوانی متد `delete`:
```php
$db = new JsonDB(__DIR__ . '/db');
$db->select('users');
/*
[
[
'first_name' => 'Ali',
'last_name' => 'AliZadeh',
'country' => 'Iran'
],
[
'first_name' => 'John',
'last_name' => 'Smith',
'country' => null
]
]
*/
$db->delete('users', ['first_name' => 'Mohammad']);
$db->select('users');
/*
[
[
'first_name' => 'Ali',
'last_name' => 'AliZadeh',
'country' => 'Iran'
],
[
'first_name' => 'John',
'last_name' => 'Smith',
'country' => null
]
]
*/
$db->delete('users', ['first_name' => 'Ali']);
$db->select('users');
/*
[
[
'first_name' => 'John',
'last_name' => 'Smith',
'country' => null
]
]
*/
$db->delete('users');
$db->select('users');
/*
[]
*/
$db->delete('users', ['invalid_column' => 'Test']); // Exception: Column invalid_column not found
```
**نکته:** در صورتی که جدول موردنظر در هر یک از متدهای `insert`، `select`، `update` یا `delete` یافت نشود، باید یک `Exception` با پیام `Table table_name not found` (`table_name` نام جدول موردنظر است) _throw_ شود.
## آنچه باید آپلود کنید
یک فایل _PHP_ که کلاس `JsonDB` درون آن قرار دارد آپلود کنید.
---
<details class="blue">
<summary>راهنمایی ۱</summary>
بدیهی است که باید یک *property* برای ذخیرهسازی محل قرارگیری فایلهای جدولها در نظر بگیریم (آن را `$db_path` مینامیم). بنابراین، متد `__construct` بهصورت زیر خواهد بود:
```php
<?php
class JsonDB
{
private $db_path;
public function __construct($db_path = __DIR__)
{
$this->db_path = $db_path;
}
}
```
اگر نام یک جدول برابر با `$table_name` باشد، مسیر فایل جدول برابر خواهد بود با:
```php
$table_filepath = $this->db_path . "/" . $table_name . ".json";
```
برای بررسی وجود فایل جدول، میتوان از تابع `file_exists` یا `is_file` استفاده کرد. در صورت عدم وجود فایل، باید یک `Exception` *throw* کرد:
```php
if (!file_exists($table_filepath)) {
throw new Exception("Table $table_filepath not found");
return false;
}
```
علت بازگشت مقدار `false`، جلوگیری از ادامهی روند اجرای متد در صورت استفاده از `try/catch` است.
برای دریافت محتویات فعلی جدول، میتوان از توابع `file_get_contents` و `json_decode` استفاده کرد:
```php
$table = json_decode(file_get_contents($table_filepath), true);
```
برای بررسی مطابقت ستونهای جدول با ستونهای ورودی، میتوان از حلقهی `foreach` استفاده کرد:
```php
$schema = $table["schema"];
foreach ($columns as $key => $value) {
if (!isset($schema[$key])) {
throw new Exception("Column $key not found");
return false;
}
}
```
برای بررسی *nullable* بودن ستونها و مقدار پیشفرض ستونها نیز میتوان از حلقهی `foreach` استفاده کرد. در حین پیمایش روی ستونها، اگر مطابقت بین جدول و ستونهای ورودی وجود داشته باشد، مقدار ستون ورودی را درون یک آرایهی موقت نگه میداریم و در غیر اینصورت، یک `Exception` *throw* میکنیم:
```php
$row = [];
foreach ($schema as $column_name => $attributes) {
if (
(
!isset($columns[$column_name])
&& !$attributes["nullable"]
&& !isset($attributes["default"])
) || (
isset($columns[$column_name])
&& $columns[$column_name] === null
&& !$attributes["nullable"]
)
) {
throw new Exception("No value provided for column $column_name");
return false;
}
if (isset($columns[$column_name])) {
$row[$column_name] = $columns[$column_name];
}
else {
if (isset($attributes["default"])) {
$row[$column_name] = $attributes["default"];
} else {
$row[$column_name] = null;
}
}
}
```
در نهایت، آرایهی `$row` را به انتهای `data` جدول اضافه کرده و با استفاده از تابع `file_put_contents` و `json_encode`، نتیجه را در فایل ذخیره میکنیم:
```php
$table["data"][] = $row;
file_put_contents($table_filepath, json_encode($table));
```
</details>
<details class="blue">
<summary>راهنمایی ۲</summary>
برای پیادهسازی متد `select`، پس از بررسی وجود جدول، آرگومان دوم را بررسی میکنیم. اگر این آرایه خالی باشد، تمامی سطرهای جدول را برمیگردانیم. در غیر اینصورت، `schema` جدول را با آرگومان دوم متد مقایسه میکنیم. اگر ستون نامعتبری یافت شود، یک `Exception` با پیغام `Column column_name not found` *throw* میکنیم:
```php
$table_filepath = $this->db_path . '/' . $table_name . '.json';
if (!file_exists($table_filepath)) {
throw new Exception("Table $table_name not found");
return false;
}
$table = json_decode(file_get_contents($table_filepath), true);
if (empty($columns)) {
return $table['data'];
}
$schema = $table['schema'];
foreach ($columns as $key => $value) {
if (!isset($schema[$key])) {
throw new Exception("Column $key not found");
return false;
}
}
```
در ادامه، سطرهای موجود در جدول را با آرگومان دوم مقایسه میکنیم. اگر ستونی یافت شود که مقدار آن با آرگومان دوم مغایرت داشته باشد، از آن صرفنظر میکنیم. کد این قسمت بهصورت زیر خواهد بود:
```php
$rows = [];
foreach ($table['data'] as $table_row) {
foreach ($columns as $key => $value) {
if ($table_row[$key] != $value) {
continue 2;
}
}
$rows[] = $table_row;
}
```
در نهایت، آرایهی `$rows` را برمیگردانیم.
</details>
<details class="blue">
<summary>راهنمایی ۳</summary>
برای پیادهسازی متد `delete` نیز ابتدا صحت نام جدول و ستونها را بررسی میکنیم:
```php
$table_filepath = $this->db_path . '/' . $table_name . '.json';
if (!file_exists($table_filepath)) {
throw new Exception("Table $table_name not found");
return false;
}
$table = json_decode(file_get_contents($table_filepath), true);
$schema = $table['schema'];
foreach ($conditions as $key => $value) {
if (!isset($schema[$key])) {
throw new Exception("Column $key not found");
return false;
}
}
```
برای حذف سطرهای موردنظر از جدول، میتوان از تابع `array_filter` استفاده کرد. اگر مقادیر ستونهای موجود در آرگومان دوم متد با ستونهای یک سطر مطابقت داشته باشد، آن سطر از ستون حذف میشود.
در پایان، برای ریست کردن `key`های جدول، از تابع `array_values` استفاده کرده و نتیجه را در فایل جدول ذخیره میکنیم:
```php
$table['data'] = array_values(
array_filter($table['data'], function ($row) use ($conditions) {
foreach ($conditions as $key => $value) {
if ($row[$key] != $value) {
return true;
}
}
return false;
})
);
file_put_contents($table_filepath, json_encode($table));
```
</details>