مدیر کتابخانهی شهر *کدنشینها* دستور داده است تا از این پس مدیریت کتابهای موجود در کتابخانه توسط یک سیستم برخط صورت گیرد. قرار بود که یکی از شهروندان شهر *کدنشینها* این سیستم را طراحی کند، اما ظاهر این سیستم آنقدر پیچیده بهنظرشان آمد که همهی آنها در حین پیادهسازی این پروژه آن را ترک کردند! اکنون از شما میخواهیم تا بخشی از این سیستم را پیادهسازی کنید.
# ساختار پروژه
پروژهی اولیه را از [اینجا](https://quera.ir/qbox/download/cD16n6QHkw/library-initial.zip) دانلود کنید.
## پایگاه داده
قرار است در این پروژه از پایگاه دادهی *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` |
2. جدول نویسندگان (`authors`):
| نام ستون | نوع | توضیح | ملاحظات |
|----------|----------------|----------------|---------------------------------------|
| `id` | `INT(11)` | شناسهی نویسنده | `UNSIGNED AUTO_INCREMENT PRIMARY KEY` |
| `name` | `VARCHAR(255)` | نام نویسنده | `NOT NULL` |
3. جدول ناشران (`publishers`):
| نام ستون | نوع | توضیح | ملاحظات |
|----------|----------------|-------------|---------------------------------------|
| `id` | `INT(11)` | شناسهی ناشر | `UNSIGNED AUTO_INCREMENT PRIMARY KEY` |
| `name` | `VARCHAR(255)` | نام ناشر | `NOT NULL` |
دسترسی به پایگاه داده از طریق نمونهای از کلاس `PDO` صورت میگیرد. این نمونه از طریق کلید `DB` توسط کلاس `Base` قابل دسترسی است:
```php
$db = Base::getInstance()->get('DB');
```
فایل موردنیاز برای ساخت جدولها را میتوانید از [اینجا](https://quera.ir/qbox/download/LdV40IvXJS/library-initial.sql) دانلود کنید.
## آدرسدهی
در این پروژه، همهی درخواستها به فایل `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` در پروژه تعریف شده است که با استفاده از آن کاربری را به آدرس موردنظر هدایت کرد. مثال:
```php
redirect('/books');
```
# پیادهسازی
## کلاس `Flash`
از این کلاس برای نمایش پیغام به کاربر پس از هدایت شدن به صفحات دیگر استفاده میشود. متدهای این کلاس را مطابق موارد زیر پیادهسازی کنید:
1. `set($type, $message)`: پیامی با محتوای `$message` از نوع `$type` (که بهصورت رشته است) را در *session* کاربر ذخیره میکند.
2. `get()`: پیام ذخیرهشده در *session* کاربر را بهصورت یک آرایهی انجمنی برمیگرداند. هر پیام فقط *یک بار* پس از *redirect* شدن کاربر به سایر صفحات نمایش داده میشود. در صورتی که پیامی ذخیره نشده بود، این متد باید مقدار `NULL` را برگرداند. نمونهای از خروجی این متد:
```php
[
'type' => 'success',
'message' => 'کتاب با موفقیت افزوده شد!'
]
```
## مدلها
سه مدل در قالب سه کلاس در دایرکتوری `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` هدایت شود.
نمایی از صفحهی اصلی پروژه:
![پیشخوان](https://quera.ir/qbox/view/C4vjvQzxNE/library-management-1.png)
نمایی از صفحهی لیست کتابها:
![لیست کتابها](https://quera.ir/qbox/view/XyohT7T0Of/library-management-2.png)
# آنچه باید آپلود کنید
فایلها و فولدرهای زیر را بدون تغییر در ساختار فولدربندی بهصورت یک فایل *Zip* ارسال کنید. از سایر فایلها صرفنظر خواهد شد:
```
app
│
├───Controllers
│ BooksController.php
│
├───helpers
│ Flash.php
│
└───Models
Author.php
Book.php
Publisher.php
```
---
<details class="blue">
<summary>راهنمایی ۱</summary>
برای پیادهسازی کلاس `Flash` میتوان هر کلید دلخواهی را در `$_SESSION` در نظر گرفت. متد `set` این کلید را مقداردهی خواهد کرد و متد `get` پس از دریافت مقدار کلید، مقدار پیام را برابر با `NULL` قرار میدهد. البته قبل از دریافت مقدار کلید، باید با استفاده از تابع `isset` بررسی کرد که آیا این کلید از قبل موجود است یا خیر. کد کلاس `Flash` بهصورت زیر خواهد بود:
```php
<?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;
}
}
```
</details>
<details class="blue">
<summary>راهنمایی ۲</summary>
کوئری دریافت تعداد نویسندگانی که حداقل یک کتاب از آنها ثبت شده بهصورت زیر است:
```sql
SELECT COUNT(DISTINCT author_id) AS authors_count FROM books
```
بنابراین، کد متد `count` از کلاس `Author` بهصورت زیر خواهد بود:
```php
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;
}
```
</details>
از آنجا که پس از فراخوانی متد `save` اطلاعات نویسنده و ناشر موجود در مدل نیز باید بهروزرسانی شوند، بنابراین کد متد `save` در کلاس `Book` بهصورت زیر خواهد بود:
```php
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;
}
```
</details>
ارسال پاسخ برای این سؤال
در حال حاضر شما دسترسی ندارید.