پیش از این توضیح دادیم که به اصطلاح 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$
فراخوانی کرده و در نهایت این متغیر را ریترن کردهایم.