تا نوروز


بخش فروش دیجی‌کالا تصمیم گرفته است که تعدادی offer استثنایی ارائه دهد. آن‌ها می‌خواهند تا عید نوروز سال آینده تخفیف ۸۰٪ روی برخی از اجناس اعمال کنند! حتی ممکن است چنین offer هایی در سال‌های آینده نیز ارائه شوند؛ لذا آن‌ها نیاز به برنامه‌ای برای محاسبه‌ی خودکار تعداد روزهای باقی‌مانده تا عید نوروز دارند.

از شما می‌خواهیم برنامه‌ای بنویسید که یک تاریخ شمسی از ورودی دریافت کرده و تعداد روزهای باقی‌مانده تا عید نوروز سال بعد، با محاسبه روز فعلی، را محاسبه کند.

نکته: فرض کنید که سال کبیسه نداریم؛ یعنی همه‌ی سال‌ها ۳۶۵ روزه هستند و شش ماه اول سال ۳۱ روزه، ۵ ماه بعدی، ۳۰ روزه و ماه آخر ۲۹ روزه است.

ورودی🔗

در یک خط از ورودی استاندارد (stdin)، رشته‌ی تاریخ (به صورت شمسی) با فرمت yyyy/mm/dd وارد می‌شود.

برای دریافت ورودی، می‌توانید از تابع readline استفاده کنید:

$date = readline();
PHP

خروجی🔗

در یک خط از خروجی استاندارد، تعداد روزهای باقی‌مانده تا عید نوروز سال بعد ورودی را چاپ کنید.

ورودی نمونه ۱:🔗

1398/11/10
Plain text

خروجی نمونه ۱:🔗

50
Plain text

ورودی نمونه ۲:🔗

1399/05/03
Plain text

خروجی نمونه ۲:🔗

239
Plain text

ورودی نمونه ۳:🔗

1396/01/01
Plain text

خروجی نمونه ۳:🔗

365
Plain text

راهنمایی ۱

برای حل این مسئله، می‌توانید آرایه‌ای شامل تعداد روزهای هر ماه در نظر بگیرید.

راهنمایی ۲

آرایه‌ی شامل تعداد روزهای هر ماه به‌صورت زیر خواهد بود:

$months = [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29];
PHP

با استفاده از تابع readline می‌توان ورودی را از stdin دریافت کرد. هم‌چنین، با استفاده از تابع explode می‌توان سال، ماه و روز را جدا کرد. در نهایت، با استفاده از تابع list می‌توان آرایه‌ی شامل سال، ماه و روز را در سه متغیر ذخیره کرد:

list($year, $month, $day) = explode('/', readline());
PHP
راهنمایی ۳

با استفاده از تابع array_slice می‌توان ماه‌های باقی‌مانده تا پایان سال را بر اساس آرایه‌ی $months جدا کرد:

$remained_months = array_slice($months, $month, count($months) - $month);
PHP

با استفاده از تابع array_sum می‌توان مجموع اعداد آرایه‌ی بالا را به‌دست آورد. بنابراین، تعداد روزهای باقی‌مانده تا عید نوروز سال بعد برابر خواهد بود با:

$remained_days = array_sum($remained_months) + $months[$month - 1] - $day + 1;
PHP

صفحه‌بندی


میلاد به‌تازگی در دیجی‌کالا به‌عنوان توسعه‌دهنده‌ی junior استخدام شده است. اولین task ای که به او واگذار شده، بازنویسی بخش pagination وب‌سایت دیجی‌استایل است. از آن‌جا که میلاد تجربه‌ی انجام این کار را ندارد، از شما می‌خواهیم تا این بخش را برای او پیاده‌سازی کنید.

ساختار پروژه🔗

پروژه‌ی اوّلیه را از این‌جا دانلود کنید. ساختار پروژه به‌صورت زیر است:

  1. فایل pagination.tpl: این فایل شامل قالب HTML بخش pagination بوده و محتوای آن به‌صورت زیر است:
<!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>
HTML
  1. فایل 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

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'], ['&laquo;', '&raquo;'], $page['text']);
        if (in_array($page['text'], ['&laquo;', '...', '&raquo;'])) {
            $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);
}
PHP

قوانین صفحه‌بندی به‌شرح زیر هستند:

  • صفحات اول و آخر باید از طریق همه‌ی صفحات در دسترس باشند.
  • دو صفحه‌ی قبلی و دو صفحه‌ی بعدی هر صفحه باید در دسترس باشند.
  • صفحاتی که فاصله‌شان از صفحه‌ی فعلی بزرگ‌تر یا مساوی ۳ است با یک دکمه‌ی ... نمایش داده می‌شوند.
  • اگر فاصله‌ی دو صفحه از یکدیگر ۲ واحد باشد، به جای دکمه‌ی ... دکمه‌ی شماره‌ی صفحه‌ی بین آن‌ها قرار می‌گیرد؛ برای مثال، بین دکمه‌های ۵ و ۷ هیچ‌گاه دکمه‌ی ... نمی‌آید.
  • دکمه‌ی prev باید در صورتی نمایش داده شود که شماره‌ی صفحه‌ی فعلی ۱ نباشد.
  • دکمه‌ی next باید در صورتی نمایش داده شود که شماره‌ی صفحه‌ی فعلی برابر با تعداد صفحات نباشد.

توجه: ترتیب آرایه‌های خروجی تابع getPaginationButtons باید به‌صورت زیر باشد:

  • دکمه‌ی prev (در صورت وجود)
  • دکمه‌ی شماره‌ی صفحات با ترتیب صعودی (می‌تواند شامل دکمه‌ی ... نیز باشد.)
  • دکمه‌ی next (در صورت وجود)

مثال ۱:🔗

<?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=');
PHP

خروجی مورد انتظار: pagination-1

مثال ۲:🔗

<?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=');
PHP

خروجی مورد انتظار: pagination-2

مثال ۳:🔗

<?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=');
PHP

خروجی مورد انتظار: pagination-3

مثال ۴:🔗

<?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=');
PHP

خروجی مورد انتظار: pagination-4

مثال ۵:🔗

<?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=');
PHP

خروجی مورد انتظار: pagination-5

  1. فایل index.php: این فایل توابع موجود در فایل pagination.php را فراخوانی کرده و از طریق آن می‌توان خروجی بخش pagination را مشاهده کرد. محتوای آن به‌صورت زیر است:
<?php

require 'pagination.php';

$pagination_template = file_get_contents('pagination.tpl');

echo renderPagination($pagination_template, 7 * 5, 5, 2, 'index.php?page=');
PHP

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

پس از پیاده‌سازی تابع getPaginationButtons، فایل pagination.php را آپلود کنید.


راهنمایی ۱

تعداد صفحات برابر خواهد بود با $total_items$per_page\lceil\frac{\$total\_items}{\$per\_page}\rceil:

$pages_count = ceil($total_items/ $per_page);
PHP
راهنمایی ۲

آرایه‌ای برای ذخیره‌سازی دکمه‌ها در نظر می‌گیریم:

$buttons = [];
PHP

اگر تعداد صفحات بیش‌تر از ۱ باشد، دکمه‌ی prev شماره‌ی $current_page - 1 را اضافه می‌کنیم:

if ($current_page > 1) {
    $buttons[] = ['text' => 'prev', 'number' => $current_page - 1];
}
PHP

پس از اضافه کردن دکمه‌های شماره و ... (در صورت نیاز)، اگر تعداد صفحات بیش‌تر از شماره‌ی صفحه‌ی فعلی باشد، دکمه‌ی next با شماره‌ی $current_page + 1 را اضافه می‌کنیم:

if ($current_page < $pages_count) {
    $buttons[] = ['text' => 'next', 'number' => $current_page + 1];
}
PHP
راهنمایی ۳

می‌دانیم حداکثر دو دکمه‌ی ... در بین دکمه‌ها موجود خواهند بود که یکی از این دکمه‌ها در بین دکمه‌های سمت چپ دکمه‌ی صفحه‌ی فعلی و دیگری در بین دکمه‌های سمت راست دکمه‌ی صفحه‌ی فعلی خواهد بود. دو flag برای این دو دکمه در نظر می‌گیریم که هرگاه این دکمه‌ها درج شوند، flag شان true می‌شود.

$leftDots = false;
$rightDots = false;
PHP

پیمایش را از شماره‌ی ۱ تا تعداد صفحات انجام می‌دهیم. اگر مقدار شماره‌ی فعلی ۱ باشد، یا اختلاف آن با شماره‌ی صفحه‌ی فعلی کم‌تر از ۳ باشد، یا مقدار شمارنده‌ی فعلی برابر با تعداد صفحات باشد، دکمه‌ای با مقدار شمارنده درج می‌کنیم.

در غیر این‌صورت، اگر مقدار شمارنده کوچک‌تر از شماره‌ی صفحه‌ی فعلی باشد و $leftDots برابر با false باشد، یک دکمه‌ی ... درج کرده و $leftDots را true می‌کنیم.

در غیر این‌صورت، اگر مقدار شمارنده بزرگ‌تر از شماره‌ی صفحه‌ی فعلی باشد و $rightDots برابر با false باشد، یک دکمه‌ی ... درج کرده و $rightDots را true می‌کنیم.

کد این بخش به‌صورت زیر خواهد بود:

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;
    }
}
PHP

تجزیه‌گر مارک‌داون


اخیراً کارمندان دیجی‌کالا تصمیم گرفته‌اند که پست‌های وبلاگ‌شان را با فرمت مارک‌داون بنویسند. مارک‌داون یک زبان نشانه‌گذاری برای قالب‌بندی متن با امکان تبدیل به فرمت‌های مختلف از جمله HTML است. با استفاده از مارک‌داون به‌راحتی می‌توان تیتر، متن‌های bold ، italic ، نقل‌قول و... ایجاد کرد. از مارک‌داون در وب‌سایت‌هایی نظیر GitHub نیز استفاده می‌شود. برای کسب اطلاعات بیش‌تر درباره‌ی مارک‌داون به این‌جا مراجعه کنید.

از شما می‌خواهیم کلاسی برای تبدیل مارک‌داون به HTML با بهره‌گیری از عبارات باقاعده (Regex) پیاده‌سازی کنید.

ساختار پروژه🔗

پروژه‌ی اولیه را می‌توانید از این‌جا دانلود کنید.

اینترفیس RuleInterface🔗

برای مدیریت روش‌های تبدیل مارک‌داون به HTML ، اینترفیسی با نام RuleInterface ایجاد کرده‌ایم؛ زیرا ممکن است در آینده با روشی غیر از عبارات باقاعده فرایند تبدیل را انجام دهیم. این اینترفیس شامل یک متد با نام parse است که باید رشته‌ای شامل محتوای مارک‌داون را دریافت کرده و خروجی موردنظر را برگرداند.

اینترفیس RegexRuleInterface🔗

این اینترفیس از اینترفیس RuleInterface پیروی کرده و وظیفه‌ی آن، مدیریت قواعد مختلف برای تبدیل مارک‌داون به HTML است. متدی با نام rule در این اینترفیس وجود دارد که باید رشته‌ای شامل یک عبارت باقاعده برای تشخیص یکی از عناصر مارک‌داون را برگرداند.

کلاس انتزاعی RegexRule🔗

این کلاس، اینترفیس RegexRuleInterface را پیاده‌سازی می‌کند. متدی انتزاعی با نام replacement در این کلاس موجود است که باید رشته‌ای شامل عبارتی که قرار است جایگزین نتایج اجرای عبارت باقاعده روی محتوای مارک‌داون شود را برگرداند.

کلاس MarkdownParser🔗

این کلاس با استفاده از نمونه‌هایی از کلاس‌های مختلف که از کلاس RegexRule ارث‌بری می‌کنند، محتوای مارک‌داون را به HTML تبدیل می‌کند. متدی با نام render در این کلاس وجود دارد که یک رشته‌ی مارک‌داون دریافت کرده و خروجی HTML آن را برمی‌گرداند.

پیاده‌سازی🔗

در حال حاضر از هفت عنصر مختلف در مارک‌داون استفاده می‌کنیم که باید قوانین استخراج و جایگزینی آن‌ها را پیاده‌سازی کنید. همه‌ی این قوانین باید تحت قالب کلاس‌هایی مجزا که از کلاس RegexRule ارث‌بری می‌کنند باشند. هر کدام از این کلاس‌ها، دو متد rule و replacement را خواهند داشت و هر کلاس باید در فایلی هم‌نام با نام کلاس پیاده‌سازی شود.

  1. تیتر (کلاس Header): این کلاس باید تیترهای موجود در متن را پردازش کند. تیترها در مارک‌داون از یک الی شش کاراکتر # به‌همراه فضای خالی در ادامه‌ی آن‌ها (به‌صورت اختیاری) و نام تیتر تشکیل شده‌اند:
# heading
##heading
###   heading
####  heading
#####    heading
######heading
Plain text

تیترها را توسط کلاس Header به تگ h3 تبدیل کنید:

<h3>heading</h3>
<h3>heading</h3>
<h3>heading</h3>
<h3>heading</h3>
<h3>heading</h3>
<h3>heading</h3>
HTML
  1. متن bold (کلاس Bold): این کلاس باید متن‌های bold را پردازش کند. متن‌های bold در مارک‌داون بین دو ** یا دو __ قرار می‌گیرند:
**bold 1**
__bold 2__
Plain text

متن‌های bold را توسط کلاس Bold به تگ b تبدیل کنید:

<b>bold 1</b>
<b>bold 2</b>
HTML
  1. متن italic (کلاس Italic): این کلاس باید متن‌های italic را پردازش کند. متن‌های italic در مارک‌داون بین دو * یا دو _ قرار می‌گیرند:
*italic 1*
_italic 2_
Plain text

متن‌های italic را توسط کلاس Italic به تگ i تبدیل کنید:

<i>italic 1</i>
<i>italic 2</i>
HTML
  1. لینک (کلاس Link): این کلاس باید لینک‌ها را پردازش کند. لینک‌ها در مارک‌داون به‌صورت زیر هستند:
[Link to Google](https://google.com)
Plain text

لینک‌ها را توسط کلاس Link به تگ a تبدیل کنید:

<a href="https://google.com">Link to Google</a>
HTML
  1. تصویر (کلاس Image): این کلاس باید تصاویر را پردازش کند. تصاویر در مارک‌داون به‌صورت زیر هستند:
![Quera's Logo](https://cdn.quera.ir/images/logo/logo-q.0-3aee17faf53e.svg)
Plain text

تصاویر را توسط کلاس Image به تگ img تبدیل کنید:

<img src="https://cdn.quera.ir/images/logo/logo-q.0-3aee17faf53e.svg" alt="Quera's Logo">
HTML
  1. کد (کلاس Code): این کلاس باید کدها را پردازش کند. کدها در مارک‌داون بین دو ` قرار می‌گیرند:
`echo "Hello, World!";`
Plain text

کدها را توسط کلاس Code به تگ code تبدیل کنید:

<code>echo "Hello, World!";</code>
HTML
  1. خط افقی (کلاس HorizontalRule): این کلاس باید خطوط افقی را پردازش کنند. خطوط افقی در مارک‌داون از ۳ یا تعداد بیش‌تری - پشت سر هم تشکیل می‌شوند:
---
-----
----
Plain text

خطوط افقی را توسط کلاس HorizontalRule به تگ hr تبدیل کنید:

<hr>
<hr>
<hr>
HTML

نکته: لزوماً نباید همه‌ی کلاس‌های بالا را پیاده‌سازی کنید. به ازای هر کلاس، امتیاز آن را به‌صورت جدا دریافت خواهید کرد.

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

پس از پیاده‌سازی کلاس‌های بالا، فایل‌های زیر را به‌صورت Zip ارسال کنید:

Header.php
Bold.php
Italic.php
Link.php
Image.php
Code.php
HorizontalRule.php
Plain text

راهنمایی ۱

عبارت باقاعده برای تشخیص تیتر (با فرض این که کاراکتر # در متن تیتر موجود نیست) به‌صورت زیر است:

#+\s*(.+)
Plain text

برای تبدیل تیتر به HTML ، باید محتویات گروه ۱ را درون تگ h3 قرار داد و نتیجه را جایگزین گروه ۰ کرد. بنابراین کد کلاس Header به‌صورت زیر خواهد بود:

<?php

class Header extends RegexRule
{
    public function rule()
    {
        return '/#+\s*(.+)/';
    }

    public function replacement()
    {
        return '<h3>$1</h3>';
    }
}
PHP
راهنمایی ۲

عبارت باقاعده برای تشخیص متن‌های bold به‌صورت زیر است:

(\*\*|__)(.+?)\1
Plain text

برای تبدیل متن bold به HTML ، باید محتویات گروه ۲ را درون تگ b قرار داد و نتیجه را جایگزین گروه ۰ کرد. بنابراین، کد کلاس Bold به‌صورت زیر خواهد بود:

<?php

class Bold extends RegexRule
{
    public function rule()
    {
        return '/(\*\*|__)(.+?)\1/';
    }

    public function replacement()
    {
        return '<b>$2</b>';
    }
}
PHP
راهنمایی ۳

عبارت باقاعده برای تشخیص لینک به‌صورت زیر است:

\[(.+?)\]\((.+?)\)
Plain text

برای تبدیل لینک به HTML ، باید محتویات گروه ۱ را درون تگ a و محتویات گروه ۲ را درون مشخصه‌ی href تگ a قرار داد و نتیجه را جایگزین گروه ۰ کرد. بنابراین، کد کلاس Link به‌صورت زیر خواهد بود:

<?php

class Link extends RegexRule
{
    public function rule()
    {
        return '/\[(.+?)\]\((.+?)\)/';
    }

    public function replacement()
    {
        return '<a href="$2">$1</a>';
    }
}
PHP

پسر J


مهدی طرفدار پروژه‌های خاص و سخت است. اخیراً یکی از دوستان صمیمی او، نیما، سفارش نوشتن یک سایت تبلیغاتی را به او داده است. از آن‌جا که مهدی نمایندگی فروش هاست و سرور ندارد، از نیما درخواست یک هاست برای میزبانی پروژه کرد. نیما هم این درخواست را با کمال میل قبول کرد و دسترسی یک هاست اشتراکی ساده با کم‌ترین امکانات ممکن را برای مهدی فرستاد. مهدی پس از بررسی‌های فراوان، متوجه شد که این هاست حتی قابلیت اتصال به MySQL را هم ندارد! لذا تصمیم گرفت که خودش اطلاعات را در قالب JSON درون فایل‌های مختلف نگه‌داری کند و از آن‌ها استفاده کند.

مهدی فرصت کمی برای انجام این پروژه دارد؛ بنابراین از شما می‌خواهیم که بخش ذخیره‌سازی اطلاعات را برای مهدی بنویسید.

جزئیات پروژه🔗

ساختار فایل‌هایی که اطلاعات در آن‌ها ذخیره می‌شوند به صورت زیر است:

db_files
├── table1.json
├── table2.json
└── table3.json
Plain text

همه‌ی جداول دیتابیس در یک دایرکتوری مشخص قرار می‌گیرند. نام هر فایل، نمایانگر نام جدول است. در مثال بالا، سه جدول با نام‌های table1، table2 و table3 وجود دارد.

هر جدول شامل خانه‌ای به نام schema است و مشخصات مربوط به ستون‌های جدول در آن قرار دارند. در schema هر جدول، مقدار پیش‌فرض ستون‌ها و امکان null بودن آن‌ها تعریف می‌شود. مقدار پیش‌فرض هر ستون در خانه‌ای به نام default و امکان null بودن مقدار ستون به صورت boolean در خانه‌ای به نام nullable ذخیره می‌شود. لزوماً خانه‌ی default برای ستون تعریف نمی‌شود، امّا خانه‌ی nullable برای تمامی ستون‌ها موجود است.

مثالی از schema یک جدول:🔗

{
    "first_name": {
        "nullable": false
    },
    "last_name": {
        "nullable": true
    },
    "country": {
        "nullable": true,
        "default": "Iran"
    }
}
JSON

هم‌چنین هر جدول خانه‌ای به نام data دارد که شامل آرایه‌ای از سطرهای موجود در جدول است.

مثالی از یک جدول:🔗

{
    "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"
    }]
}
JSON

کل پروژه را در قالب کلاسی به نام 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:🔗

$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
PHP
  • متد select را به گونه‌ای پیاده‌سازی کنید که نام جدول و آرایه‌ای از ستون‌ها و مقادیر متناظرشان را دریافت کرده و سطرهایی که مقادیر ستون‌هایشان با مقادیر ستون‌های ورودی یکسان است را در قالب آرایه برگرداند. لزوماً مقادیر همه‌ی ستون‌ها به‌عنوان ورودی به متد داده نمی‌شوند. فرض بر این است که بین شرط‌ها جهت یافتن سطرهای خروجی AND وجود دارد. در صورتی که ستونی به‌عنوان ورودی به متد داده نشود، باید همه‌ی سطرها به‌عنوان خروجی تابع return شود. در صورتی که ستونی به متد داده شود و آن ستون در schema جدول موجود نباشد، باید یک Exception با پیام Column column_name not found (column_name نام ستون موردنظر است) throw شود.

مثالی از فراخوانی متد select:🔗

$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
PHP
  • متد update مقدار یک یا چند ستون را در سطرهایی که مشخص می‌کنیم تغییر می‌دهد. این متد شامل سه آرگومان بوده که آرگومان اول نام جدول موردنظر برای به‌روزرسانی است، آرگومان دوم شامل مقادیر جدید ستون‌هایی است که قرار است تغییر کنند و آرگومان سوم مشخص می‌کند که ستون‌های چه سطرهایی باید تغییر کنند؛ به طوری که هر سطری که مقادیر ستون‌هایش برابر با مقادیر ستون‌های آرگومان سوم باشد باید به‌روزرسانی شود. لزوماً مقادیر همه‌ی ستون‌های جدول در آرگومان‌های دوم و سوّم موجود نیستند. اگر آرگومان سوّم به متد داده نشد، باید همه‌ی سطرهای جدول به‌روزرسانی شوند. فرض بر این است که بین شرط‌ها جهت یافتن سطرها جهت به‌روزرسانی AND وجود دارد. در صورتی که ستونی در آرگومان دوم یا سوّم موجود باشد و آن ستون در schema جدول موجود نباشد، باید یک Exception با پیام Column column_name not found (column_name نام ستون موردنظر است) throw شود.

مثالی از فراخوانی متد update:🔗

$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
PHP
  • متد delete را به گونه‌ای پیاده‌سازی کنید که نام جدول و آرایه‌ای از ستون‌ها و مقادیر متناظرشان را دریافت کرده و سطرهایی که مقادیر ستون‌هایشان با مقادیر ستون‌های ورودی یکسان است را از جدول حذف کند. لزوماً مقادیر همه‌ی ستون‌ها به‌عنوان ورودی به متد داده نمی‌شوند. فرض بر این است که بین شرط‌ها جهت یافتن سطرها برای حذف AND وجود دارد. در صورتی که ستونی به‌عنوان ورودی به متد داده نشود، باید همه‌ی سطرها از جدول حذف شوند. در صورتی که ستونی به متد داده شود و آن ستون در schema جدول موجود نباشد، باید یک Exception با پیام Column column_name not found (column_name نام ستون موردنظر است) throw شود.

مثالی از فراخوانی متد delete:🔗

$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
PHP

نکته: در صورتی که جدول موردنظر در هر یک از متدهای insert، select، update یا delete یافت نشود، باید یک Exception با پیام Table table_name not found (table_name نام جدول موردنظر است) throw شود.

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

یک فایل PHP که کلاس JsonDB درون آن قرار دارد آپلود کنید.


راهنمایی ۱

بدیهی است که باید یک property برای ذخیره‌سازی محل قرارگیری فایل‌های جدول‌ها در نظر بگیریم (آن را $db_path می‌نامیم). بنابراین، متد __construct به‌صورت زیر خواهد بود:

<?php

class JsonDB
{
    private $db_path;

    public function __construct($db_path = __DIR__)
    {
        $this->db_path = $db_path;
    }
}
PHP

اگر نام یک جدول برابر با $table_name باشد، مسیر فایل جدول برابر خواهد بود با:

$table_filepath = $this->db_path . "/" . $table_name . ".json";
PHP

برای بررسی وجود فایل جدول، می‌توان از تابع file_exists یا is_file استفاده کرد. در صورت عدم وجود فایل، باید یک Exception throw کرد:

if (!file_exists($table_filepath)) {
    throw new Exception("Table $table_filepath not found");
    return false;
}
PHP

علت بازگشت مقدار false، جلوگیری از ادامه‌ی روند اجرای متد در صورت استفاده از try/catch است.

برای دریافت محتویات فعلی جدول، می‌توان از توابع file_get_contents و json_decode استفاده کرد:

$table = json_decode(file_get_contents($table_filepath), true);
PHP

برای بررسی مطابقت ستون‌های جدول با ستون‌های ورودی، می‌توان از حلقه‌ی foreach استفاده کرد:

$schema = $table["schema"];
foreach ($columns as $key => $value) {
    if (!isset($schema[$key])) {
        throw new Exception("Column $key not found");
        return false;
    }
}
PHP

برای بررسی nullable بودن ستون‌ها و مقدار پیش‌فرض ستون‌ها نیز می‌توان از حلقه‌ی foreach استفاده کرد. در حین پیمایش روی ستون‌ها، اگر مطابقت بین جدول و ستون‌های ورودی وجود داشته باشد، مقدار ستون ورودی را درون یک آرایه‌ی موقت نگه می‌داریم و در غیر این‌صورت، یک Exception throw می‌کنیم:

$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;
        }
    }
}
PHP

در نهایت، آرایه‌ی $row را به انتهای data جدول اضافه کرده و با استفاده از تابع file_put_contents و json_encode، نتیجه را در فایل ذخیره می‌کنیم:

$table["data"][] = $row;
file_put_contents($table_filepath, json_encode($table));
PHP
راهنمایی ۲

برای پیاده‌سازی متد select، پس از بررسی وجود جدول، آرگومان دوم را بررسی می‌کنیم. اگر این آرایه خالی باشد، تمامی سطرهای جدول را برمی‌گردانیم. در غیر این‌صورت، schema جدول را با آرگومان دوم متد مقایسه می‌کنیم. اگر ستون نامعتبری یافت شود، یک Exception با پیغام Column column_name not found throw می‌کنیم:

$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;
}
PHP

در نهایت، آرایه‌ی $rows را برمی‌گردانیم.

راهنمایی ۳

برای پیاده‌سازی متد delete نیز ابتدا صحت نام جدول و ستون‌ها را بررسی می‌کنیم:

$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;
    }
}
PHP

برای حذف سطرهای موردنظر از جدول، می‌توان از تابع array_filter استفاده کرد. اگر مقادیر ستون‌های موجود در آرگومان دوم متد با ستون‌های یک سطر مطابقت داشته باشد، آن سطر از ستون حذف می‌شود.

در پایان، برای ریست کردن keyهای جدول، از تابع array_values استفاده کرده و نتیجه را در فایل جدول ذخیره می‌کنیم:

$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));
PHP

مدیریت کتاب‌خانه


مدیر کتاب‌خانه‌ی شهر کدنشین‌ها دستور داده است تا از این پس مدیریت کتاب‌های موجود در کتاب‌خانه توسط یک سیستم برخط صورت گیرد. قرار بود که یکی از شهروندان شهر کدنشین‌ها این سیستم را طراحی کند، اما ظاهر این سیستم آن‌قدر پیچیده به‌نظرشان آمد که همه‌ی آن‌ها در حین پیاده‌سازی این پروژه آن را ترک کردند! اکنون از شما می‌خواهیم تا بخشی از این سیستم را پیاده‌سازی کنید.

ساختار پروژه🔗

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

پایگاه داده🔗

قرار است در این پروژه از پایگاه داده‌ی MySQL استفاده شود. سه جدول با مشخصات زیر از قبل طراحی شده‌اند و شامل سطرهایی به‌عنوان مقادیر اولیه هستند:

  1. جدول کتاب‌ها (books):
نام ستون نوع توضیح ملاحظات
id INT(11) شناسه‌ی کتاب UNSIGNED AUTO_INCREMENT PRIMARY KEY
name VARCHAR(255) عنوان کتاب NOT NULL
author_id INT(11) شناسه‌ی نویسنده‌ی کتاب UNSIGNED NOT NULL
publisher_id INT(11) شناسه‌ی ناشر کتاب UNSIGNED NOT NULL
quantity INT(11) موجودی کتاب UNSIGNED NOT NULL
  1. جدول نویسندگان (authors):
نام ستون نوع توضیح ملاحظات
id INT(11) شناسه‌ی نویسنده UNSIGNED AUTO_INCREMENT PRIMARY KEY
name VARCHAR(255) نام نویسنده NOT NULL
  1. جدول ناشران (publishers):
نام ستون نوع توضیح ملاحظات
id INT(11) شناسه‌ی ناشر UNSIGNED AUTO_INCREMENT PRIMARY KEY
name VARCHAR(255) نام ناشر NOT NULL

دسترسی به پایگاه داده از طریق نمونه‌ای از کلاس PDO صورت می‌گیرد. این نمونه از طریق کلید DB توسط کلاس Base قابل دسترسی است:

$db = Base::getInstance()->get('DB');
PHP

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

آدرس‌دهی🔗

در این پروژه، همه‌ی درخواست‌ها به فایل index.php ارسال می‌شوند و متد مناسب از یک کنترلر بر اساس URL صدا زده می‌شود. چندین route از قبل در پروژه تعریف شده‌اند که به‌شرح زیر هستند:

  • GET /: صفحه‌ی پیشخوان
  • GET /books: صفحه‌ی لیست کتاب‌ها
  • GET /books/add: فرم افزودن کتاب
  • GET /books/reserve/:id: رزرو کردن کتاب
  • GET /books/unreserve/:id: افزودن موجودی کتاب
  • GET /books/delete/:id: حذف کتاب
  • GET /authors: صفحه‌ی لیست نویسندگان
  • GET /publishers: صفحه‌ی لیست ناشران

تابعی با نام redirect در پروژه تعریف شده است که با استفاده از آن کاربری را به آدرس موردنظر هدایت کرد. مثال:

redirect('/books');
PHP

پیاده‌سازی🔗

کلاس Flash🔗

از این کلاس برای نمایش پیغام به کاربر پس از هدایت شدن به صفحات دیگر استفاده می‌شود. متدهای این کلاس را مطابق موارد زیر پیاده‌سازی کنید:

  1. set($type, $message): پیامی با محتوای $message از نوع $type (که به‌صورت رشته است) را در session کاربر ذخیره می‌کند.
  2. get(): پیام ذخیره‌شده در session کاربر را به‌صورت یک آرایه‌ی انجمنی برمی‌گرداند. هر پیام فقط یک بار پس از redirect شدن کاربر به سایر صفحات نمایش داده می‌شود. در صورتی که پیامی ذخیره نشده بود، این متد باید مقدار NULL را برگرداند. نمونه‌ای از خروجی این متد:
[
    'type' => 'success',
    'message' => 'کتاب با موفقیت افزوده شد!'
]
PHP

مدل‌ها🔗

سه مدل در قالب سه کلاس در دایرکتوری app/Models موجود هستند که باید متدهای درون آن‌ها را پیاده‌سازی کنید. همه‌ی این مدل‌ها از کلاس Model ارث‌بری می‌کنند و می‌توان فیلدهای آن‌ها را از طریق یک آرایه‌ی انجمنی در constructor آن‌ها مقداردهی کرد. این مدل‌ها و متدهای موردنیاز هر کدام به‌شرح زیر هستند:

  1. مدل Book:
    • فیلدها:
      • id: شناسه‌ی کتاب (از نوع عدد)
      • name: نام کتاب (از نوع رشته)
      • author: نویسنده‌ی کتاب (از نوع آبجکتی از مدل Author)
      • publisher: ناشر کتاب (از نوع آبجکتی از مدل Publisher)
      • quantity: موجودی کتاب (از نوع عدد صحیح)
    • متدها:
      • all(): همه‌ی کتاب‌های موجود در جدول books را در قالب آرایه‌ای از آبجکت‌های مدل Book برمی‌گرداند.
      • count(): تعداد همه‌ی کتاب‌ها (صرف‌نظر از موجودی‌شان) را برمی‌گرداند.
      • find($id: کتابی که شناسه‌ی آن برابر با مقدار $id است را برمی‌گرداند. در صورتی که چنین کتابی موجود نباشد، باید یک Exception با پیغام Book not found پرتاب شود.
      • save(): تغییرات مشخصات کتاب را در جدول ذخیره می‌کند. تضمین می‌شود که شناسه‌ی کتاب، شناسه‌ی نویسنده و شناسه‌ی ناشر از قبل موجود است.
      • delete(): کتابی که شناسه‌ی آن با شناسه‌ی کتاب فعلی برابر است را از جدول حذف می‌کند. در صورتی که چنین کتابی در جدول موجود نباشد، نباید تغییری صورت گیرد.
  2. مدل Author:
    • فیلدها:
      • id: شناسه‌ی نویسنده (از نوع عدد)
      • name: نام نویسنده (از نوع رشته)
      • books_count: تعداد کتاب‌های متمایز از این نویسنده (از نوع عدد صحیح)
    • متدها:
      • all(): همه‌ی نویسندگان موجود در جدول authors که حداقل یک کتاب از آن‌ها در لیست کتاب‌ها موجود است را در قالب آرایه‌ای از آبجکت‌های مدل Author برمی‌گرداند.
      • count(): تعداد همه‌ی نویسندگانی که حداقل یک کتاب از آن‌ها در لیست کتاب‌های کتاب‌خانه موجود است را برمی‌گرداند.
  3. مدل Publisher:
    • فیلدها:
      • id: شناسه‌ی ناشر (از نوع عدد)
      • name: نام ناشر (از نوع رشته)
      • books_count: تعداد کتاب‌های متمایز از این ناشر (از نوع عدد صحیح)
    • متدها:
      • all(): همه‌ی ناشران موجود در جدول publishers که حداقل یک کتاب از آن‌ها در لیست کتاب‌ها موجود است را در قالب آرایه‌ای از آبجکت‌های مدل Publisher برمی‌گرداند.
      • count(): تعداد همه‌ی ناشرانی که حداقل یک کتاب از آن‌ها در لیست کتاب‌های کتاب‌خانه موجود است را برمی‌گرداند.

کنترلرها🔗

کنترلرها وظیفه‌ی دریافت اطلاعات از مدل‌ها، پردازش آن‌ها و ارسال نتایج به viewها را دارند. چهار کنترلر در قالب چهار کلاس در این پروژه موجود هستند که خوشبختانه سه کنترلر از قبل به‌طور کامل پیاده‌سازی شده‌اند. متد add از کنترلر BooksController را مطابق موارد زیر پیاده‌سازی کنید:

این متد زمانی فراخوانی می‌شود که متد درخواست POST باشد. اطلاعات زیر از طریق $_POST قابل دسترسی خواهند بود:

  • name: نام کتاب
  • author: نام نویسنده‌ی کتاب
  • publisher: نام ناشر کتاب

اگر حداقل یکی از فیلدهای بالا خالی باشند، باید پیغامی از نوع danger و با محتوای همه‌ی اطلاعات باید وارد شوند. در session کاربر ذخیره شده و کاربر به آدرس /books/add هدایت شود.

در صورت مقداردهی شدن همه‌ی فیلدها، کتابی با اطلاعات ورودی در جدول books با موجودی اولیه‌ی 1 درج کنید. اگر نام نویسنده‌ی کتاب یا نام ناشر کتاب از قبل در جدول authors یا publishers موجود باشند، این مقادیر نباید مجدداً به این جداول اضافه شوند و باید از شناسه‌ی قبلی آن‌ها برای درج استفاده شود. پس از درج اطلاعات، باید پیغامی از نوع success و با محتوای کتاب با موفقیت افزوده شد! در session کاربر ذخیره شده و کاربر به آدرس /books هدایت شود.

نمایی از صفحه‌ی اصلی پروژه:

پیشخوان

نمایی از صفحه‌ی لیست کتاب‌ها:

لیست کتاب‌ها

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

فایل‌ها و فولدرهای زیر را بدون تغییر در ساختار فولدربندی به‌صورت یک فایل Zip ارسال کنید. از سایر فایل‌ها صرف‌نظر خواهد شد:

app
│
├───Controllers
│       BooksController.php
│
├───helpers
│       Flash.php
│
└───Models
        Author.php
        Book.php
        Publisher.php
Plain text

راهنمایی ۱

برای پیاده‌سازی کلاس Flash می‌توان هر کلید دلخواهی را در $_SESSION در نظر گرفت. متد set این کلید را مقداردهی خواهد کرد و متد get پس از دریافت مقدار کلید، مقدار پیام را برابر با NULL قرار می‌دهد. البته قبل از دریافت مقدار کلید، باید با استفاده از تابع isset بررسی کرد که آیا این کلید از قبل موجود است یا خیر. کد کلاس Flash به‌صورت زیر خواهد بود:

<?php

class Flash
{
    public static function set($type, $message)
    {
        $_SESSION['flash'] = compact('type', 'message');
    }

    public static function get()
    {
        $flash = null;
        if (isset($_SESSION['flash'])) {
            $flash = $_SESSION['flash'];
            $_SESSION['flash'] = null;
        }

        return $flash;
    }
}
PHP
راهنمایی ۲

کوئری دریافت تعداد نویسندگانی که حداقل یک کتاب از آن‌ها ثبت شده به‌صورت زیر است:

SELECT COUNT(DISTINCT author_id) AS authors_count FROM books
SQL

بنابراین، کد متد count از کلاس Author به‌صورت زیر خواهد بود:

public function count()
{
    $sql = 'SELECT
            COUNT(DISTINCT author_id) AS authors_count FROM books';
    $result = Base::getInstance()->get('DB')->prepare($sql); 
    $result->execute(); 
    $count = $result->fetchColumn();

    return $count;
}
PHP

از آن‌جا که پس از فراخوانی متد save اطلاعات نویسنده و ناشر موجود در مدل نیز باید به‌روزرسانی شوند، بنابراین کد متد save در کلاس Book به‌صورت زیر خواهد بود:

public function save()
{
    $stmt = Base::getInstance()->get('DB')->prepare(
        'UPDATE books SET name = :name, author_id = :author_id,
        publisher_id = :publisher_id, quantity = :quantity WHERE id = :id'
    );
    $stmt->execute([
        ':id' => $this->id,
        ':name' => $this->name,
        ':author_id' => $this->author->id,
        ':publisher_id' => $this->publisher->id,
        ':quantity' => $this->quantity
    ]);
    $book = $this->find($this->id);
    $this->author = $book->author;
    $this->publisher = $book->publisher;
}
PHP