یکی از فروشگاههای زنجیرهای قصد دارد طراحی پلتفرم فروشگاه اینترنتی خود را به شما بسپارد.
نیازمندیهای نرم افزار این مجموعه شامل مدیریت محصولات فروشگاه و سبد خرید کاربران می باشد.
برای پیاده سازی به این نکات توجه کنید:
+ کاربران برای خرید محصولات نیازی به احراز هویت ندارند.
+ در حال حاضر برای ایجاد محصول جدید نیازی به کنترل سطح دسترسی نداریم. به عبارت دیگر، کلیه کاربران امکان ایجاد محصول جدید را دارند.
+ کاربران از هر محصول فقط به تعداد مشخصی می توانند در سبد خرید خود اضافه کنند. میزان محدودیت خرید برای هر محصول در هنگام تعریف آن مشخص می شود.
## ساختار پروژه
ساختار اولیه نرم افزار با استفاده از مدل `MVC` پیاده سازی شده است و به صورت زیر می باشد:
+ دایرکتوری `Controllers`: کلاسهای کنترلر داخل این دایرکتوری قرار گرفته اند. تمامی این کلاسها تحت فضای نام (namespace) `Controllers` قرار دارند. توجه کنید که شما مجاز به تغییر هیچ کدام از کلاسهای کنترلر نیستید.
+ دایرکتوری `Models`: کلاسهای مدل که مسئول ارتباط با پایگاه داده هستند، داخل این دایرکتوری قرار گرفته اند. تمامی این کلاسها تحت فضای نام (namespace) `Models` قرار دارند.
+ دایرکتوری `Views`: فایلهای نمایش برنامه در این دایرکتوری قرار می گیرند.
+ فایل `index.php`: نقطه ورودی و مدیریت کلیه درخواستها در سیستم.
+ فایل `helpers.php`: این فایل مجموعهای از توابع کمکی می باشد.
+ فایل `autoload`: کلیه توابع مربوط به بارگذاری خودکار فایلهای `php` مورد نیاز، در این فایل قرار می گیرند.
# آدرس دهی
کلیه درخواستها به فایل `index.php` ارجاع می شوند.
سپس با استفاده از آدرس `URL`، کنترلر متناسب برای مدیریت درخواست فراخوانی میشود.
کنترلر و متد درخواست شده با استفاده از متغیرهای زیر و به صورت `Query String` در آدرس صفحه قرار میگیرند.
+ `controller` : نام کنترلر موجود در دایرکتوری `Controllers` به صورت `lowercase` در این متغیر قرار می گیرد
+ `method`: نام متد فراخوانی شده در این متغیر قرار می گیرد.
##### نمونه :
برای فراخوانی متد `index` از کنترلر `Products` آدرس دهی باید به صورت زیر باشد:
`http://test.dev/index.php?controller=products&method=index`
** `test.dev` یک دامنه فرضی می باشد.
## پایگاه داده
برای ذخیره سازی اطلاعات می خواهیم از پایگاه داده `Sqlite3` استفاده نماییم.
برای این منظور نیاز به دو جدول با ساختار زیر داریم.
##### 1. جدول محصولات (`products`)
| ملاحضات | تعریف | نوع | اسم ستون |
|:-:|:-:|:-:|:-:|
| `PRIMARY KEY` | شناسهی محصول | `integer` | `id` |
| `NOT NULL UNIQUE` | عنوان محصول | `text` | `title` |
| `NOT NULL` | قیمت محصول | `integer` | `price` |
| `NOT NULL` | حداکثر تعداد قابل خریداری برای هر شخص | `integer` | `maximum_count` |
##### 2. جدول سبد خرید (`carts`)
| ملاحضات | تعریف | نوع | اسم ستون |
|:-:|:-:|:-:|:-:|
| `PRIMARY KEY` | شناسه | `integer` | `id` |
| `NOT NULL` | شناسه سبد خرید کاربر | `text` | `cart_identifier` |
| `NOT NULL` | شناسه محصول خریداری شذه | `integer` | `product_id` |
| `NOT NULL` | تعداد محصول خریداری شده | `integer` | `product_count` |
شما باید فایل دیتابیس را به نام `db.sqlite` در دایرکتوری اصلی، در کنار فایل `index.php` ایجاد نمایید.
## پروژه اولیه
پروژه اولیه را میتوانید از [اینجا](https://quera.ir/qbox/download/czj7wMTymN/init.zip) دانلود کنید.
## پیاده سازی
#### 3. پیاده سازی توابع کمکی
در فایل `helpers.php` نیار به پیاده سازی چند تابع داریم.
+ `resolve_request_parameter($name, $default = null)`
این تابع را به گونهای پیاده سازی نمایید که بتواند مقدار `$name` را در متغیر `$_GET` یا `$_POST` جستحو نماید و مقدار آنرا برگرداند.
در صورتی که این متغیر یافت نشد، باید مقدار `$default` بازگردانده شود.
+ `redirect($controller = '', $method = '')`
این تابع را به گونهای پیاده سازی نمایید که با دریافت نام کنترلر و متد اکشن مورد نیاز، ابتدا آدرس صحیح را ایجاد کرده.
سپس کاربر را به آن آدرس ***redirect*** نماید.
+ `set_alert($alert)`
این تابع را به گونهای پیاده سازی نمایید که رشته دریافت شده را به گونهای در سیستم ذخیره نماید، که این متغیر تنها یک بار بعد از ***redirect*** به کاربر نمایش داده شود.
برای این کار می توانید از `Session` کمک بگیرید.
+ `get_alert()`
این تابع را به گونه ای پیاده سازی نمایید که پیام ثبت شده توسط `set_alert` را برای نمایش در فایلهای `view` بازگرداند.
توجه کنید که هر پیام تنها یکبار قابلیت نمایش دارد و در صورت ***refresh*** کردن صفحه، هیچ پیامی نباید نمایش داده شود.
همجنین در صورتی که هیچ پیامی تنظیم نشده بود، باید مقدار `null` برگشت داده شود.
+ `get_cart_identifier()`
این تابع را به گونهای پیاده سازی نمایید که یک شناسه یکتا برای هر کاربر ایجاد نموده و مقدار آن را بازگرداند.
محصولات موجود در سبد خرید هر کاربر با استفاد از این شناسه، تفکیک می شود.
توجه کنید که برای هر کاربر تنها یکبار شناسه صادر می شود و این شناسه باید در تمام مراحل بعدی خرید بدون تغییر باقی بماند.
برای این کار می توانید از `Session` کمک بگیرید.
مقدار بازگشتی این تابع باید یک رشته از اعداد یا حروف 5 حرفی باشد.
#### 1. پیاده سازی ساختار autoload
در هیچ کدام از کلاسها نباید از روشهای بارگزاری مستقیم فایل (شامل require, include, require_once, include_once) استفاده نمود.
شما باید با استفاده از متدهای پیش بینی شده در `php` برنامه را به گونهای تغییر دهید که در صورت نیاز به هر کلاس، برنامه به طور خودکار فایل مربوط به آن کلاس را بارگذاری نماید.
توجه کنید که نام `namespace` ها برابر نام دایرکتوری و نام `class` ها برابر نام فایل می باشد.
توابع پیاده سازی شده را در فایل `autoload.php` قرار دهید.
#### 2. پیاده سازی ساختار مدلها
همه کلاسهای مدلها از کلاس `DatabaseModel` ارث بری می کنند.
شما باید منطق این کلاس را به گونهای پیاده سازی نمایید که عملیاتهای زیر را به درستی انجام دهد.
+ `getConnection()`
این متد باید یک شی از توع `SQLite3` که به دیتابیس متصل شده است را برگرداند.
+ `$attributes`
با استفاده از توابع پیش بینی شده در `php` این کلاس را به گونهای توسعه دهید، که متغیرهای جدید منتسب شده را در آرایه `$attributes` قرار دهد.
همچنین در صورت دسترسی به متغیرهای ثبت نشده در کلاس، سعی کند نام متغیر را از آرایه `$attributes` بخواند.
نمونه:
```php
$product = new \Models\Product();
$product->title = "Pencil";
$product->price = 10000
```
بعد ار اجرای این کد متغیر `$attributes` باید به صورت آرایه ای شامل عناصر زیر باشد:
```php
[
"title": "Pencil",
"price": 10000",
]
```
همجنین در صورت فراخوانی متغیرهای دیگر، آرایه `$attributes` باید مورد جستجو قرار گیرد.
```php
echo $product->title;
echo $product->price;
```
خروجی این کد باید به صورتزیر باشد:
```text
"Pencil"
10000
```
ضمنا توجه کنید که اگر در هنگام نمونه سازی از کلاس `Model` ها، لیستی از مقادیر را به تابع سازنده ارسال کنیم، این مقادیر در آرایه `$attributes` قرار می گیرند.
#### 3. نصب سیستم
برای نصب سیستم باید به آدرس `index.php?controller=install&method=index` مراجعه نمایید.
نصب سیستم شامل پروسه ایجاد جداول پایگاه داده می باشد.
تابع `buildSchema` در هر کلاس مدل مسئولیت ایجاد جدول مربوط به خود را دارد.
تابع `buildSchema` موجود در کلاس `\Models\Product` و `\Models\Cart` را به گونهای تغییر دهید که بعد از فراخوانی جداول مورد نیاز را ایجاد کند.
#### 4. نمایش لیست محصولات
برای نمایش لیست محصولات باید به آدرس `index.php?controller=products&method=index` مراجعه نمایید.
برای نمایش صحیح محصولات، متد `all` از کلاس `Models\Product` را به گونهای تغییر دهید که یک آرایه شامل لیستی از instance های `Models\Product` را با مقادیر درست بازگرداند.
هر کدام از instance های محصولات باید شامل مقادیر زیر باشد:
```php
$product->id
$product->title
$product->price
$product->maximum_count
```
محصولات باید به ترتیب درج شدن، روی لیست نمایش داده شود.
توجه کنید که شما مجاز به تغییر کنترلر `Controllers\Products` نمی باشید.
تمامی تغییرات باید روی مدل `Models\Product` اعمال گردد.
#### 5. افزودن محصول جدید
برای نمایش فرم محصول جدید باید به آدرس `index.php?controller=products&method=form` مراجعه نمایید.
متد `create` از مدل `Models\Product` را به گونهای تغییر دهید، که با دریافت پارامترهای نوشته شده، اطلاعات محصول جدید را در دیتابیس ذخیره نماید.
سپس یک نمونه از `Models\Product` را با کلیه اطلاعات محصول ثبت شده، شامل `id`, `title`, `price`, `maximum_count` باز گرداند.
#### 6. دریافت اطلاعات محصول
متد `find` از کلاس `Models\Product` را به گونه ای پیاده سازی نمایید، که با دریافت `id` محصول، کلیه اطلاعات محصول را از دیتابیس گرفته و به صورت یک شی `Models\Product` بازگرداند.
در صورتی که هیچ محصولی با `id` ذکر شده یافت نشد، سیستم باید یک `Exception` با پیغام `Model Not Found` پرتاب نماید.
#### 7. خرید محصول
در لیست محصولات با کلیک بر روی لینک `Buy` به آدرس `index.php?controller=carts&method=add` هدایت می شوید.
شما باید تابع `purchase` از مدل `Models\Cart` را به گونهای پیاده سازی نمایید که با دریافت شناسه سبد خرید کاربر و یک شی `Models\Product` به عنوان ورودی اقدامات زیر را انجام دهد:
+ در صورت که کاربر قبلا از این محصول در سبد خود اضافه نکرده بود، این محصول را به سبد خرید کاربر اضافه نماید.
+ در صورتی که کاربر قبلا این محصول را به سبد خرید خود اضافه کرده بود، باید مقدار `product_count` آن یک واحد افزایش یابد.
+ در صورتی که کاربر قبلا حداکثر مقدار قابل سفارش از این محصول را به سبد خود اضافه کرده بود، باید یک `Exception` با پیام `Maximum Count Reached` پرتاب شود.
#### 8. نمایش محصولات موجود در سبد خرید
برای نمایش لیست محصولات سبد خرید کاربر باید به آدرس `index.php?controller=carts&method=index` مراجعه نمایید.
برای نمایش لیست سبد خرید باید تابع `getProducts` از کلاس `Models\Cart` را به گونه ای تغییر دهید که یک آرایه شامل نمونههایی از کلاس `Models\Cart` را بازگرداند.
هر instance از کلاس `Models\Cart` باید شامل اطلاعات زیر باشد.
```php
$cart->id; // id ثبت شده در جدول carts
$cart->title; // عنوان محصول خریداری شده
$cart->price; // قیمت هر عدد از محصول خریداری شده ( قیمت FEE)
$cart->product_id; // id محصول انتخاب شده در حدول products
$cart->product_count; // تعداد سفارش از این محصول
$cart->total_price; // حاصل ضرب قیمت محصول در تعداد سفارش محصول
```
همچنین شما باید متد `calculateTotalPrice` را به گونه ای پیاده سازی نمایید که با دریافت لیست تولید شده توسط تابع `getProducts`، بتواند مجموع کل قیمت سفارش را محاسبه نماید.
#### 9. حذف یک محصول از سبد خرید
در لیست سبد خرید با کلیک روی لینک Add به متد `purchase` که قبلا پیاده سازی نمودیم، هدایت می شویم.
در صورت کلیک روی لینک Remove به آدرس `index.php?controller=carts&method=remove` هدایت می شویم.
در این مسیر، محصول انتخاب شده باید از سبد خرید به طور کلی حذف شود.
برای این کار باید متد `remove` از کلاس `Models\Cart` را به گونه ای تغییر دهید، که با دریافت شناسه سبد خرید کاربر و یک instance از `Models\Product`، اقدامات زیر را انجام دهد:
+ در صورتی که محصول در سبد خرید کاربر وجود داشت، از سبد خرید حذف می شود.
+ در صورتی که این محصول در سبد خرید کاربر وجود نداشت، یک `Exception` با پیام `Product Not Available` باید پرتاب شود.
## ارسال نتیجه
در نهایت فایلها و فولدرهای زیر را بدون تغییر ساختار فولدر بندی، در یک فایل ***zip*** ارسال نمایید.
```text
/Models/Cart.php
/Models/Product.php
/Models/DatabaseModel.php
/helpers.php
/autoload.php
```