پیش از این توضیح دادیم که به اصطلاح Business Logic یا به عبارتی «آن چیزی که اپلیکیشن به خاطرش توسعه یافته است.» در لایهٔ مدل قرار میگیرد و اساساً میتوان گفت که مدل همچون مغز اپلیکشن است. بخشی از منطق اپلیکیشنی که در حال توسعهٔ آن هستیم به ارتباط با دیتابیس مرتبط است به طوری که قصد داریم مدلی داشته باشیم تا از آن طریق کلیهٔ کاربران سایت را از دیتابیس فراخوانی کرده و در ویویی تحت عنوان users.php نمایش دهیم که در آموزش گذشته ایجاد کردیم.
اما پیش از شروع توسعهٔ لایهٔ مدل، نیاز است تا ماژول جدید Base را در فایل composer.json معرفی نماییم چرا که از این مرحلهٔ آموزش به بعد قصد داریم فایلهایی ایجاد نماییم که باید در سایر بخشها استفاده گردند و از همین روی نیاز است تا به صورت خودکار ایمپورت شوند. برای این منظور، فایل composer.json را به صورت زیر تکمیل میکنیم:
{
"autoload": {
"psr-4": {
"Core\\": "app/Core",
"Base\\": "app/Base"
}
}
}همانطور که میبینیم، مشابه فولدر Core که پیش از این تعریف کرده بودیم، در انتهای خط چهارم یک , قرار داده سپس در خط بعد یک جفت Key/Value به صورتی که در بالا مشاهده میشود خواهیم ساخت به طوری که از این پس با استفاده از نِیماِسپیس Base به فولدر Base داخل فولدر app ارجاع خواهیم داد. اکنون به منظور تکمیل فرآیند افزودن نِیماِسپیس Base نیاز است تا مجدد کامند زیر را اجرا نماییم:
/var/www/mvc$ composer dump-autoload -oاز این پس نِیماِسپیس جدید فعال بوده و میتوانیم از کلیهٔ کلاسهایی که داخل پوشهٔ Base میسازیم هم در داخل این پوشه و هم در خارج از آن و در کل پروژه استفاده نماییم.
ساخت دیتابیس
ابتدا در محیط phpmyadmin دیتابیسی با نامی دلخواه همچون mvc تعریف میکنیم که حاوی جدولی به نام users با اسکمای زیر خواهد بود:
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| first_name | varchar(255) | NO | | NULL | |
| last_name | varchar(255) | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+در ادامه، به صورت دستی چند داده وارد جدول users میکنیم به طوری که خواهیم داشت:
+----+------------+-----------+
| id | first_name | last_name |
+----+------------+-----------+
| 1 | Behzad | Moradi |
| 2 | Sahand | Imani |
+----+------------+-----------+از این پس، شرایط لازم برای ساخت مدل به منظور ارتباط با دیتابیس و فِچ کردن (فراخوانی) دادهها از جدول users و نمایش آنها در ویویی تحت عنوان users.php مهیا است (به خاطر داشته باشیم که در حین ساخت دیتابیس، Collation آن را برابر با utf8_general_ci قرار دهیم تا امکان ذخیرهٔ کاراکترهای فارسی را نیز داشته باشیم.)
ساخت کلاس Database
علاوه بر ساخت سه لایهٔ مدل، ویو و کنترلر در کامپوننت Base نیاز است تا پوشهٔ دیگری داخل این فولدر تحت عنوان Config بسازیم که میتوان فایلهای پیکربندی وب اپلیکیشن خود را در آن ذخیره ساخت به طوری که در حال حاضر حاوی فایلی خواهد بود به نام Database.php که این وظیفه را دارا است تا ارتباط با سیستم مدیریت پایگاه دادهٔ MySQL را هندل کند (جهت آشنایی با این سیستم مدیریت دیتابیس، میتوانید به دورهٔ آموزش SQL و MySQL مراجعه نمایید.) در ادامه، فایل Database.php را به صورت زیر تکمیل میکنیم:
<?php
namespace Base\Config;
class Database
{
private $host = 'localhost';
private $databaseName = 'mvc';
private $username = 'root';
private $password = '123456';
private $connection = null;
public function connect()
{
try {
$this->connection = new \PDO("mysql:host=$this->host;dbname=$this->databaseName", $this->username, $this->password);
$this->connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
} catch (\PDOException $exception) {
echo $exception->getMessage();
}
return $this->connection;
}
}طبق روال معمول پس از ایجاد یک کلاس جدید، ابتدا نِیماِسپیس آن را تعریف میکنیم تا کامپوزر بتواند محل قرارگیری این فایل را بیابد و از آنجا که در ماژول Base قرار داریم و پیش از این هم این ماژول را در فایل composer.json تعریف کردیم، پس از نوشتن کیورد namespace ابتدا نام این ماژول سپس نام فولدری که داخل آن قرار داریم یا به عبارتی Config را مینویسیم.
همانطور که ملاحظه میشود، کلاسی داریم تحت عنوان Database که این وظیفه را دارا است تا با دیتابیس ارتباط برقرار سازد و از همین روی تعدادی پراپرتی از جنس private به منظور نگهداری کانفیگ دیتابیس تعریف کردهایم.
| نکته |
به خاطر داشته باشیم که به جای استفاده از متغیرها برای ذخیرهسازی کانفیگ دیتابیس، از کانستنتها نیز میتوانیم استفاده نماییم چرا که این مقادیر در طول اجرای برنامه ثابت بوده و از همین روی شاید استفاده از کانستنت در تعریف آنها منطقیتر باشد! |
localhost برای پراپرتی host$ به لایبرری PDO دستور میدهیم تا از طریق لوکالهاست به دیتابیس متصل گردد. پیش از این دیتابیسی با نام mvc ساختیم و این نام را برای پراپرتی databaseName$ در نظر خواهیم گرفت و به ترتیب نام کاربری و رمزعبور اتصال به محیط phpmyadmin را در پراپرتیهای username$ و password$ ذخیره ساخته و در نهایت یک پراپرتی تحت عنوان connection$ با مقدار پیشفرض null میسازیم که قرار است تا نگهدارندهٔ ارتباط با دیتابیس باشد.در این کلاس متدی داریم به نام ()connect که داخل آن قصد داریم تا با استفاده از لایبرری PDO با دیتابیس خود ارتباط برقرار سازیم که برای آشنایی بیشتر با این لایبرری و پیش از مطالعهٔ ادامهٔ این آموزش، توصیه میکنیم به مقالهٔ ارتباط با دیتابیس در PHP از طریق لایبرری PDO مراجعه نمایید.
در این متد از بلوک try و catch استفاده کردهایم که این امکان را اختیارمان قرار میدهد تا به سادگی بتوانیم در صورت بروز هر گونه اِسکسپشنی آن را هندل نماییم. در همین راستا، داخل بلوک try از طریق دستور ()new \PDO آبجکتی از روی این کلاس ساخته و آن را به پراپرتی connection$ منتسب میکنیم (جهت دسترسی به پراپرتیهای یک کلاس، از دستور <-this$ استفاده مینماییم.)
به طور کلی، کلاس PDO سه پارامتر ورودی میگیرد که پارامتر اول مرتبط با تنظیمات سرور و نام دیتابیس است به طوری که داریم:
"mysql:host=$this->host;dbname=$this->databaseName"همانطور که میبینیم، ابتدا نام درایور که در این مثال mysql است را نوشته بلافاصله علامت : را قرار میدهیم و پس از آن کیورد host را نوشته و دستور this->host$ که حاوی مقدار localhost است را پس از یک علامت = قرار میدهیم. در ادامه یک علامت ; قرار داده و کیورد dbname را نوشته و طبق منوال قبل نام دیتابیس را درج میکنیم.
در ادامه، پارامترهای دوم و سوم این کلاس به ترتیب به نام کاربری و رمزعبور اختصاص دارند و از همین روی پراپرتیهای مرتبط را درج مینماییم.
در کلاس PDO متدی تعبیه شده به نام ()setAttribute که با استفاده از آن میتوان یکسری تنظیمات را فعال ساخت. به طور مثال، اتریبوت PDO::ATTR_ERRMODE به منظور فعالسازی ارورها و اتریبوت PDO::ERRMODE_EXCEPTION به منظور فعالسازی اِکسپشنها مورد استفاده قرار میگیرد.
در بلوک catch آبجکتی تحت عنوان exception$ از روی کلاس PDOException ساختهایم و چنانچه در پروسهٔ ارتباط با دیتابیس با اِکسپشنی مواجه شویم، با منتسب کردن متد ()getMessage به آبجکت exception$ از ماهیت مشکل ایجادشده اطلاع خواهیم یافت.
در نهایت هم پراپرتی connection$ که از این پس حاوی آبجکتی از جنس کلاس PDO است که دربرگیرندهٔ کانکشن (ارتباط) با دیتابیس میباشد ریترن خواهد شد و هر کجا که از این متد استفاده نماییم، به کانکشن دیتابیس دسترسی خواهیم داشت.
اهمیت استفاده از دیزاین پترن سینگلتون در حین ارتباط با دیتابیس
به طور کلی، دیزاین پترنها دربرگیرندهٔ سولوشنهایی مورداعتماد و تستشده هستند که توسط دولوپرهای حرفهای ابداع گردیدهاند و پیروی از آنها این تضمین را ایجاد میکند که توسعهٔ نرمافزارمان به شکلی اصولی صورت خواهد گرفت و یکی از دیزاین پترنهای پرکاربرد Singleton نام دارد با این تعریف که:
دیزاین پترن سینگلتون به منظور محدود کردن ساخت آبجکت از روی برخی کلاسهای هزینهبر به کار گرفته میشود به طوری که تنها یک آبجکت از کلاس مذکور ساخته شده و مورد استفاده قرار میگیرد و از همین روی، پیادهسازی دیزاین پترن سینگلتون در چنین شرایطی از اهمیت بالایی برخوردار بوده و منجر بدین خواهد شد تا آبجکتهای متعددی از روی کلاسی خاص ساخته نشده و تنها یک آبجکت ایجاد شده و مورد استفاده قرار گیرد.
از جمله مواردی که این الگوی طراحی کاربرد دارد، ارتباط با دیتابیس است به طوری که میتوان گفت به منظور بهینه کردن این فریمورک، نیاز است تا بخش ارتباط با دیتابیس آن را با پیروی از الگوی طراحی سینگلتون پیادهسازی نماییم که برای این منظور، توصیه میشود به آموزش آشنایی با الگوی طراحی Singleton مراجعه نمایید.
ساخت مدل برای فراخوانی لیست کاربران از دیتابیس
در این مرحله از توسعهٔ این وب اپلیکیشن بر پایهٔ معماری MVC، قصد داریم تا مدلی به منظور فِچ کردن لیست کاربران از جدول users ایجاد نماییم. برای این منظور، در روت فولدر Base فولدری تحت عنوان Models ساخته سپس فایلی تحت عنوان User.php حاوی کدهای زیر داخل آن ایجاد مینماییم:
<?php
namespace Base\Models;
use Core\Interfaces\UserInterface;
class User implements UserInterface
{
private $connection;
private $usersTable = 'users';
public function __construct($database)
{
$this->connection = $database;
}
public function fetch()
{}
public function fetchById(int $id)
{}
public function update(array $data)
{}
public function delete(int $id)
{}
}پس از تعریف نِیماِسپیس این فایل، همنام با نام این فایل کلاسی تحت عنوان User میسازیم که از اینترفیس UserInterface که در آموزشهای گذشته ساختیم ایمپلیمنت میکند و بالتبع کلیهٔ متدهای تعریفشده داخل این اینترفیس میباید در بدنهٔ این نیز تعریف شوند (با توجه به اینکه از اینترفیس UserInterface داخل این کلاس استفاده نمودهایم، مسلماً نیاز به use کردن آن داریم که این کار را در خط چهارم انجام دادهایم.)
این کلاس دارای دو پراپرتی تحت عناوین connection$ و usersTable$ از جنس private است؛ به عبارتی، فقط و فقط داخل همین کلاس به آنها دسترسی خواهیم داشت. پراپرتی اول از طریق کانستراکتور مقداردهی خواهد شد بدین شکل که یک پارامتر ورودی تحت عنوان database$ برای کانستراکتور در نظر گرفتهایم که در حین ساخت آبجکت از روی کلاس User میباید متغیری حاوی کانکشن به دیتابیس را به این کلاس پاس دهیم و بلافاصله این پارامتر در بدنهٔ کانستراکتور به پراپرتی connection$ منتسب خواهد شد. پراپرتی دوم حاوی استرینگ users است به طوری که نام جدولی که قصد داریم به آن کوئری بزنیم را در این پراپرتی ذخیره کردهایم.
با توجه به اینکه در اینترفیس مربوط به این کلاس چهار متد مختلف تعریف کردهایم، طبیعتاً موظف به تعریف کلیهٔ آنها از بدنهٔ کلاس هستیم ولو اینکه نخواهیم از آنها استفاده نماییم (به این کار اصطلاحاً Anti Pattern یا به عبارتی «سَبک غیرحرفهای» توسعهٔ نرمافزار گفته میشود. در حقیقت، روش توسعهٔ اینترفیسها باید به گونهای باشد که در هر کلاسی که از آنها ایمپلیمنت میکنیم، هیچ متدی اضافه نباشد که در همین راستا توصیه میکنیم به آموزش درآمدی بر قانون Interface Segregation مراجعه نمایید.)
در این دورهٔ آموزشی صرفاً متد ()fetch را توسعه خواهیم داد و از همین روی کلاس User را به صورت زیر تکمیل میکنیم:
<?php
namespace Base\Models;
use Core\Interfaces\UserInterface;
class User implements UserInterface
{
private $connection;
private $usersTable = 'users';
public function __construct($database)
{
$this->connection = $database;
}
public function fetch()
{
$query = "SELECT * FROM $this->usersTable";
$statement = $this->connection->prepare($query);
$statement->execute();
return $statement;
}
public function fetchById(int $id)
{}
public function update(array $data)
{}
public function delete(int $id)
{}
}داخل بدنهٔ متد ()fetch ابتدا متغیری تحت عنوان query$ تعریف کردهایم که حاوی یک کد اسکیوال ساده است مبنی بر اینکه «تمام دیتای جدول کاربران انتخاب گردد.» پراپرتی connection$ که از این پس حاوی آبجکتی منتسب به کلاس PDO است این امکان را در اختیارمان میگذارد تا به متدهای این کلاس دسترسی داشته باشیم. در همین راستا، در ادامه متغیری ساختهایم به نام statement$ که در آن متد ()prepare را روی پراپرتی connection$ فراخوانی کرده سپس به عنوان پارامتر ورودی، متغیر query$ را به آن پاس دادهایم و در ادامه متد ()execute را روی متغیر statement$ فراخوانی کرده و در نهایت این متغیر را ریترن کردهایم.
