ساخت یک برنامه CRUD با PHP و رابط PDO

CRUD Application with PHP, PDO, and MySQL

PDO-CRUD-App

آماده سازی محیط کار

سلام به همراهان گرامی روکسو. در این مقاله می خواهیم یک برنامه ساده CRUD را از ابتدا با زبان PHP و از طریق رابط PDO بنویسیم. در این آموزش از هیچ فریم ورکی استفاده نخواهیم کرد. ممکن است با مفهوم CRUD آشنا نباشید بنابراین ابتدا آن را برایتان توضیح می دهم.

CRUD مخفف چهار عملیات اصلی برای تغییر داده در پایگاه داده است: Create («ساختن» و وارد کردن داده در دیتابیس)، Read («خواندن» و دریافت اطلاعات از دیتابیس)، Update («به روز رسانی» و ویرایش داده ها در دیتابیس) و نهایتا Delete («حذف» داده ها از دیتابیس). بنابراین برنامه CRUD برنامه ای است که چهار عملیات اصلی کار با پایگاه داده را انجام بدهد.

ما برای این آموزش از زبان PHP و رابط PDO و همچنین از پایگاه داده MySQL استفاده می کنیم. یک جدول آماده MySQL به نام contacts نیز در اختیار شما قرار می دهم تا با آن کار کنید. این جدول شامل id و name و email و phone و title و created (زمان عضویت) می باشد. شما در این آموزش با موارد زیر آشنا خواهید شد:

  • ساخت رکورد (ردیف) در پایگاه MySQL و جدول Contacts
  • خواندن رکورد از MySQL و نمایش آن ها در مرورگر
  • به روز رسانی رکوردها در MySQL و جدول Contacts
  • حذف رکوردهای MySQL از جدول Contacts
  • کار با درخواست های GET و POST برای تعامل با فرم برنامه
  • Prepared statements برای جلوگیری از حملات SQL Injection (مطالعه کامل در این مورد از این لینک)

همچنین برای شروع کدنویسی با من به چند مورد نیاز دارید:

  • نصب یک وب سرور: پیشنهاد من برای این قسمت دانلود و نصب XAMPP است اما نرم افزار های معادل مانند WAMP و غیره نیز کار می کنند.
  • نصب زبان PHP: بهتر است از آخرین نسخه PHP استفاده کنید (اگر XAMPP را نصب کرده اید نیازی به این مورد نیست چرا که XAMPP به صورت خودکار این کار را انجام می هد).
  • Extension PDO: به صورت پیش فرض در XAMPP فعال است اما اگر نبود باید آن را به صورت دستی فعال کنید.

برای شروع کار به آدرس C:\xampp\htdocs بروید و ساختار پوشه و فایل های زیر را ایجاد کنید:

\-- phpcrud
  |-- index.php
  |-- create.php
  |-- read.php
  |-- update.php
  |-- delete.php
  |-- functions.php
  |-- style.css

وظیفه این فایل ها به ترتیب عبارت اند از:

  • php: صفحه اصلی (Home page) برنامه ما است.
  • php: ایجاد رکورد جدید در پایگاه داده با یک فرم HTML و ارسال درخواست PSOT.
  • php: دریافت رکوردها از پایگاه داده و ساخت pagination.
  • php: به روز رسانی رکوردها در پایگاه داده و ارسال داده ها از طریق درخواست های POST.
  • php: تایید و حذف رکوردها از طریق id و درخواست GET برای گرفتن id.
  • php: نگهدارنده توابع پرکاربرد مانند اتصال به پایگاه داده است تا هر بار کدها را تکرار نکنیم.
  • css: استایل های برنامه ما در این فایل قرار دارد تا ظاهر مناسبی را به آن بدهیم.

ساخت پایگاه داده و جدول contacts

برای کار با یک برنامه CRUD قطعا به پایگاه داده و جدول هایش نیاز داریم. از آنجایی که من در این آموزش از XAMPP استفاده می کنم، این قسمت را بر اساس XAMPP توضیح می دهم اما شما می توانید توضیحات من را در روش های دیگر نیز پیاده سازی کنید.

ابتدا به آدرس http://localhost/phpmyadmin/ بروید تا برنامه phpMyAdmin برایتان باز شود. سپس روی Database کلیک کنید. با کلیک روی این قسمت می توانید گزینه Create database را مشاهده کنید که باید نام خاصی را برای این پایگاه داده انتخاب کنیم. من نام phpcrud را انتخاب کرده و collation آن را روی utf8mb4_general_ci یا روی utf8_general_ci تنظیم کنید. collation در واقع نحوه ارتباط پایگاه داده با برنامه ما را از نظر Unicode تعیین می کند بنابراین باید utf8 را انتخاب کنیم تا بتوانیم با زبان فارسی نیز کار کنیم. در نهایت روی Create کلیک کنید تا پایگاه داده شما ساخته شود. روی این پایگاه داده جدید کلیک کرده و به سربرگ (تب) SQL بروید. سپس دستور زیر را در آن paste کنید تا یک جدول آماده به نام contacts داشته باشیم:

CREATE TABLE IF NOT EXISTS `contacts` (
	`id` int(11) NOT NULL AUTO_INCREMENT,
  	`name` varchar(255) NOT NULL,
  	`email` varchar(255) NOT NULL,
  	`phone` varchar(255) NOT NULL,
  	`title` varchar(255) NOT NULL,
  	`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
	PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

INSERT INTO `contacts` (`id`, `name`, `email`, `phone`, `title`, `created`) VALUES
(1, 'John Doe', 'johndoe@example.com', '2026550143', 'Lawyer', '2019-05-08 17:32:00'),
(2, 'David Deacon', 'daviddeacon@example.com', '2025550121', 'Employee', '2019-05-08 17:28:44'),
(3, 'Sam White', 'samwhite@example.com', '2004550121', 'Employee', '2019-05-08 17:29:27'),
(4, 'Colin Chaplin', 'colinchaplin@example.com', '2022550178', 'Supervisor', '2019-05-08 17:29:27'),
(5, 'Ricky Waltz', 'rickywaltz@example.com', '7862342390', '', '2019-05-09 19:16:00'),
(6, 'Arnold Hall', 'arnoldhall@example.com', '5089573579', 'Manager', '2019-05-09 19:17:00'),
(7, 'Toni Adams', 'alvah1981@example.com', '2603668738', '', '2019-05-09 19:19:00'),
(8, 'Donald Perry', 'donald1983@example.com', '7019007916', 'Employee', '2019-05-09 19:20:00'),
(9, 'Joe McKinney', 'nadia.doole0@example.com', '6153353674', 'Employee', '2019-05-09 19:20:00'),
(10, 'Angela Horst', 'angela1977@example.com', '3094234980', 'Assistant', '2019-05-09 19:21:00'),
(11, 'James Jameson', 'james1965@example.com', '4002349823', 'Assistant', '2019-05-09 19:32:00'),
(12, 'Daniel Deacon', 'danieldeacon@example.com', '5003423549', 'Manager', '2019-05-09 19:33:00');

این جدول دارای داده های از پیش آماده شده است تا کار یادگیری را برای ما آسان کند. همانطور که قبلا گفتم خود جدول دارای 6 ستون است (id, name, email, phone, title, created) که در phpMyAdmin به شکل زیر دیده می شود:

ظاهر پایگاه داده در phpMyAdmin
ظاهر پایگاه داده در phpMyAdmin

ایجاد استایل های برنامه

برای ساده تر شدن کار، استایل های برنامه را از قبل برایتان آماده کردم بنابراین باید آن ها را کپی کرده و درون فایل style.css خود قرار دهید:

* {
  	box-sizing: border-box;
  	font-family: -apple-system, BlinkMacSystemFont, "segoe ui", roboto, oxygen, ubuntu, cantarell, "fira sans", "droid sans", "helvetica neue", Arial, sans-serif;
  	font-size: 16px;
  	-webkit-font-smoothing: antialiased;
  	-moz-osx-font-smoothing: grayscale;
}
body {
  	background-color: #FFFFFF;
  	margin: 0;
}
.navtop {
  	background-color: #3f69a8;
  	height: 60px;
  	width: 100%;
  	border: 0;
}
.navtop div {
  	display: flex;
  	margin: 0 auto;
  	width: 1000px;
  	height: 100%;
}
.navtop div h1, .navtop div a {
  	display: inline-flex;
  	align-items: center;
}
.navtop div h1 {
  	flex: 1;
  	font-size: 24px;
  	padding: 0;
  	margin: 0;
  	color: #ecf0f6;
  	font-weight: normal;
}
.navtop div a {
  	padding: 0 20px;
  	text-decoration: none;
  	color: #c5d2e5;
  	font-weight: bold;
}
.navtop div a i {
  	padding: 2px 8px 0 0;
}
.navtop div a:hover {
  	color: #ecf0f6;
}
.content {
  	width: 1000px;
  	margin: 0 auto;
}
.content h2 {
  	margin: 0;
  	padding: 25px 0;
  	font-size: 22px;
  	border-bottom: 1px solid #ebebeb;
  	color: #666666;
}
.read .create-contact {
  	display: inline-block;
  	text-decoration: none;
  	background-color: #38b673;
  	font-weight: bold;
  	font-size: 14px;
  	color: #FFFFFF;
  	padding: 10px 15px;
  	margin: 15px 0;
}
.read .create-contact:hover {
  	background-color: #32a367;
}
.read .pagination {
  	display: flex;
  	justify-content: flex-end;
}
.read .pagination a {
  	display: inline-block;
  	text-decoration: none;
  	background-color: #a5a7a9;
  	font-weight: bold;
  	color: #FFFFFF;
  	padding: 5px 10px;
  	margin: 15px 0 15px 5px;
}
.read .pagination a:hover {
  	background-color: #999b9d;
}
.read table {
  	width: 100%;
  	padding-top: 30px;
  	border-collapse: collapse;
}
.read table thead {
  	background-color: #ebeef1;
  	border-bottom: 1px solid #d3dae0;
}
.read table thead td {
  	padding: 10px;
  	font-weight: bold;
  	color: #767779;
  	font-size: 14px;
}
.read table tbody tr {
  	border-bottom: 1px solid #d3dae0;
}
.read table tbody tr:nth-child(even) {
  	background-color: #fbfcfc;
}
.read table tbody tr:hover {
  	background-color: #376ab7;
}
.read table tbody tr:hover td {
  	color: #FFFFFF;
}
.read table tbody tr:hover td:nth-child(1) {
  	color: #FFFFFF;
}
.read table tbody tr td {
  	padding: 10px;
}
.read table tbody tr td:nth-child(1) {
  	color: #a5a7a9;
}
.read table tbody tr td.actions {
  	padding: 8px;
  	text-align: right;
}
.read table tbody tr td.actions .edit, .read table tbody tr td.actions .trash {
  	display: inline-flex;
  	text-align: right;
  	text-decoration: none;
  	color: #FFFFFF;
  	padding: 10px 12px;
}
.read table tbody tr td.actions .trash {
  	background-color: #b73737;
}
.read table tbody tr td.actions .trash:hover {
  	background-color: #a33131;
}
.read table tbody tr td.actions .edit {
  	background-color: #37afb7;
}
.read table tbody tr td.actions .edit:hover {
  	background-color: #319ca3;
}
.update form {
  	padding: 15px 0;
  	display: flex;
  	flex-flow: wrap;
}
.update form label {
  	display: inline-flex;
  	width: 400px;
  	padding: 10px 0;
  	margin-right: 25px;
}
.update form input {
  	padding: 10px;
  	width: 400px;
  	margin-right: 25px;
  	margin-bottom: 15px;
  	border: 1px solid #cccccc;
}
.update form input[type="submit"] {
  	display: block;
  	background-color: #38b673;
  	border: 0;
  	font-weight: bold;
  	font-size: 14px;
  	color: #FFFFFF;
  	cursor: pointer;
  	width: 200px;
	margin-top: 15px;
}
.update form input[type="submit"]:hover {
  	background-color: #32a367;
}
.delete .yesno {
  	display: flex;
}
.delete .yesno a {
  	display: inline-block;
  	text-decoration: none;
  	background-color: #38b673;
  	font-weight: bold;
  	color: #FFFFFF;
  	padding: 10px 15px;
  	margin: 15px 10px 15px 0;
}
.delete .yesno a:hover {
  	background-color: #32a367;
}

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

تعریف توابع پرکاربرد

اولین قدم برای شروع تکمیل کردن فایل functions.php است. همانطور که در قسمت های قبل توضیح دادم این فایل نگهدارنده توابع کاربردی ما است تا کدهای خود را درون تک تک فایل ها تکرار نکنیم. هر چقدر کدهای شما کوتاه تر باشد، برنامه منظم تر و تمیز تر خواهد بود. این فایل در مجموع 3 تابع را خواهد داشت که اولی برای اتصال به پایگاه داده و دومی و سومی قالبی برای header و Footer برنامه خواهند بود.

تابع اول:

<?php
function pdo_connect_mysql() {
    $DATABASE_HOST = 'localhost';
    $DATABASE_USER = 'root';
    $DATABASE_PASS = '';
    $DATABASE_NAME = 'phpcrud';
    try {
    	return new PDO('mysql:host=' . $DATABASE_HOST . ';dbname=' . $DATABASE_NAME . ';charset=utf8', $DATABASE_USER, $DATABASE_PASS);
    } catch (PDOException $exception) {
    	// If there is an error with the connection, stop the script and display the error.
    	die ('Failed to connect to database!');
    }
}

من قبلا در سری آموزشی کامل PDO در مورد این کد توضیح مفصل داده ام (ر.ک این لینک) اما به طور خلاصه این کد را توضیح می دهم. در این تابع ابتدا اطلاعات پایگاه داده را ذخیره کرده ام که برای XAMPP به شکل بالا است. یعنی localhost آدرس host ما و root نام کاربری ما است. همچنین پایگاه داده XAMPP بدون رمز است به همین دلیل یک رشته خالی را برای رمز عبور قرار داده ایم و نام پایگاه داده را هم که در قسمت قبل قبل phpcrud گذاشته بودیم بنابراین باید آن را دقیقا همان مقدار وارد کنیم. اگر اطلاعات شما با من متفاوت است (مثلا برای پایگاه داده خود رمز عبور تعیین کرده اید یا نام پایگاه داده را تغییر داده اید و غیره) باید در کد بالا اطلاعات خودتان را وارد کنید.

سپس برای برقرار اتصال از یک بلوکه try and catch استفاده کرده ایم تا بتوانیم خطا های احتمالی را مدیریت کنیم. تقریبا تمام برنامه نویسان حرفه ای برای برقرار اتصال با پایگاه داده از try and catch استفاده می کنند بنابراین بهتر است شما هم به آن عادت کنید. توجه داشته باشید که ما new PDO را در کد بالا return کرده ام. اگر قسمت return را نداشته باشیم دیگر به اتصال ایجاد شده دسترسی نخواهیم داشت، بلکه اتصالی ایجاد کرده ایم که نمی توانیم کاری با آن انجام بدهیم. بقیه کدها هم ساختار PDO است که باید با آن آشنا باشید. اگر این ساختار را متوجه نمی شوید به دوره آموزشی PDO سری بزنید.

تابع دوم مخصوص header صفحات است. در برنامه ما تمام صفحات یک header یکسان دارند:

function template_header($title) {
echo <<<EOT
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>$title</title>
		<link href="style.css" rel="stylesheet" type="text/css">
		<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.1/css/all.css">
	</head>
	<body>
    <nav class="navtop">
    	<div>
    		<h1>Website Title</h1>
            <a href="index.php"><i class="fas fa-home"></i>Home</a>
    		<a href="read.php"><i class="fas fa-address-book"></i>Contacts</a>
    	</div>
    </nav>
EOT;
}

همانطور که می بینید این تابع به جز برگرداندن مقداری کد HTML کار خاص دیگری انجام نمی دهد. من از کتابخانه font awesome نیز استفاده کرده ام تا پیاده سازی آیکون ها راحت تر شود. تابع مخصوص footer از این هم ساده تر است:

function template_footer() {
echo <<<EOT
    </body>
</html>
EOT;
}
?>

توجه داشته باشید که هر سه تابع (سه قسمت کدهای بالا) را به همین ترتیب درون فایل functions.php قرار بدهید.

کدنویسی Home Page

صفحه اصلی یا Home page وب سایت، اولین صفحه ای است که کاربران با ورود به وب سایت مشاهده می کنند. از آنجایی که نام فایل مربوط به آن را index.php گذاشته ایم با مراجعه به آدرس http://localhost/phpcrud/ فایل به صورت خودکار اجرا می شود. درون این فایل محتویات زیر را قرار دهید:

<?php
include 'functions.php';
// Your PHP code here.

// Home Page template below.
?>

<?=template_header('Home')?>

<div class="content">
	<h2>Home</h2>
	<p>Welcome to the home page!</p>
</div>

<?=template_footer()?>

اولین کاری که در این کدها انجام داده ام، include کردن فایل functions.php بود تا بتوانیم header و footer سایت را داشته باشیم. این کد یک صفحه بسیار ساده به ما می دهد. اگر به آدرس http://localhost/phpcrud/ بروید چنین صفحه ای را مشاهده می کنید:

ظاهر Home Page
ظاهر Home Page

اگر شما دوست داشته باشید می توانید محتویات دیگری را به این صفحه اضافه کنید اما هدف من از ساختن این صفحه، رفتن به صفحات دیگر است بنابراین چیزی برای نمایش ندارم.

کدنویسی read.php

کار این صفحه نمایش رکوردهای جدول contacts است تا ما ببینیم چه اطلاعاتی را در پایگاه داده داریم. ما این اطلاعات را در یک جدول ساده HTML نمایش می دهیم. کد زیر را برای این کار آماده کرده ایم بنابراین آن را درون فایل read.php خود کپی کنید:

<?php
include 'functions.php';
// Connect to MySQL database
$pdo = pdo_connect_mysql();
// Get the page via GET request (URL param: page), if non exists default the page to 1
$page = isset($_GET['page']) && is_numeric($_GET['page']) ? (int)$_GET['page'] : 1;
// Number of records to show on each page
$records_per_page = 5;

این تنها قسمت اول فایل read.php است. در اینجا ابتدا فایل functions.php را برای اتصال به پایگاه داده وارد کرده ایم. دقت کنید که صدا زدن تابع pdo_connect_mysql را برابر یک متغیر به نام pdo قرار داده ایم. به نظر شما چرا؟ اگر یادتان باشد کدهای این تابع را به صورت return کردن یک new PDO نوشته بودیم بنابراین باید یک متغیر را برابر تابع قرار دهیم تا اتصال ما به پایگاه داده را به خودمان برگرداند. اگر تابع را فقط صدا بزنیم، دیگر چیزی نخواهیم داشت که با آن به پایگاه داده متصل شویم و به عبارتی نقطه اتصال گم می شود.

متغیر page نیز مسئول تعیین کردن شماره صفحه است. برای این متغیر گفته ایم اگر پارامتر page درون url مرورگر بود (دستور isset برای GET) و همچنین مقدار این پارامتر عددی بود (is_numeric) مقدار متغیر page را برابر مقدار پارامتر page در URL قرار بده و در غیر این صورت آن را برابر 1 بگذار. در نهایت متغیری به نام records_per_page داریم که تعداد رکوردهای نمایش داده شده در هر صفحه را مشخص می کند و ما آن را روی 5 گذاشته ایم. به طور مثال اگر 10 رکورد (ردیف) در پایگاه داده داشته باشیم، به صورت دو صفحه 5 رکوردی نمایش داده می شود و کاربر می تواند بین این صفحات جا به جا شود (شبیه به pagination).

حالا به قسمت بعدی فایل read.php می رسیم که مسئول دریافت رکوردها از پایگاه داده است. در قسمت بعد این قسمت را کدنویسی کرده و فایل read.php را تکمیل می کنیم.

آخرین کدی که در این فایل نوشتیم، کد زیر بود:

<?php
include 'functions.php';
// Connect to MySQL database
$pdo = pdo_connect_mysql();
// Get the page via GET request (URL param: page), if non exists default the page to 1
$page = isset($_GET['page']) && is_numeric($_GET['page']) ? (int)$_GET['page'] : 1;
// Number of records to show on each page
$records_per_page = 5;

من این کد را در قسمت قبل توضیح دادم بنابراین تکرار مکررات را کنار می گذارم. قسمت بعدی که باید به این کد اضافه کنیم این قسمت است:

// Prepare the SQL statement and get records from our contacts table, LIMIT will determine the page
$stmt = $pdo->prepare('SELECT * FROM contacts ORDER BY id LIMIT :current_page, :record_per_page');
$stmt->bindValue(':current_page', ($page-1)*$records_per_page, PDO::PARAM_INT);
$stmt->bindValue(':record_per_page', $records_per_page, PDO::PARAM_INT);
$stmt->execute();
// Fetch the records so we can display them in our template.
$contacts = $stmt->fetchAll(PDO::FETCH_ASSOC);

اگر با PDO آشنا باشید حتما می دانید که در کد بالا از prepared statement ها استفاده شده است (از نوع named ها). ما کوئری خود را prepare کرده ایم و سپس با استفاده از bindValue مقدار مورد نظر را به آن چسبانده ایم. مقداری زیر شاید شما را سر در گم کند:

$stmt->bindValue(':current_page', ($page-1)*$records_per_page, PDO::PARAM_INT);

در واقع current_page باید ایندکس آغازین در جدول ما باشد بنابراین از فرمول ریاضی بالا برای محاسبه آن استفاده کرده ایم. این یک فرمول ثابت است و میتوانید همیشه از آن برای پروژه هایتان استفاده کنید. مثلا اگر در صفحه 1 باشیم، 1 منهای 1 می شود صفر که در records_per_page ضرب شده و باز هم صفر می شود، بنابراین اگر در صفحه 1 باشیم باید از ایندکس صفر در پایگاه داده شروع کنیم که دقیقا همان چیزی است که می خواهیم. قسمت PDO::PARAM_INT نیز نوع داده current_page را مشخص می کند که طبیعتا یک عدد است بنابراین INT را برایش تعیین کرده ایم. در نهایت نتایج را به صورت PDO::FETCH_ASOC (آرایه متناظر) دریافت کرده ایم تا در جدول خود نمایش بدهیم. همانطور که گفتم تمام این موارد در سری آموزشی PDO توضیح داده شده است.

حالا قسمت زیر را نیز به فایل read.php اضافه کنید:

// Get the total number of contacts, this is so we can determine whether there should be a next and previous button
$num_contacts = $pdo->query('SELECT COUNT(*) FROM contacts')->fetchColumn();
?>

کوئری بالا تعداد تمام رکوردهای موجود در جدول contacts را به ما می دهد. در ضمن از آنجایی که از سمت کاربر داده ای دریافت نمی کنیم از prepared statement ها استفاده نکرده ام و مستقیما query را صدا زده ام. حالا کد زیر را به read.php اضافه کنید:

<?=template_header('Read')?>

<div class="content read">
	<h2>Read Contacts</h2>
	<a href="create.php" class="create-contact">Create Contact</a>
	<table>
        <thead>
            <tr>
                <td>#</td>
                <td>Name</td>
                <td>Email</td>
                <td>Phone</td>
                <td>Title</td>
                <td>Created</td>
                <td></td>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($contacts as $contact): ?>
            <tr>
                <td><?=$contact['id']?></td>
                <td><?=$contact['name']?></td>
                <td><?=$contact['email']?></td>
                <td><?=$contact['phone']?></td>
                <td><?=$contact['title']?></td>
                <td><?=$contact['created']?></td>
                <td class="actions">
                    <a href="update.php?id=<?=$contact['id']?>" class="edit"><i class="fas fa-pen fa-xs"></i></a>
                    <a href="delete.php?id=<?=$contact['id']?>" class="trash"><i class="fas fa-trash fa-xs"></i></a>
                </td>
            </tr>
            <?php endforeach; ?>
        </tbody>
    </table>
	<div class="pagination">
		<?php if ($page > 1): ?>
		<a href="read.php?page=<?=$page-1?>"><i class="fas fa-angle-double-left fa-sm"></i></a>
		<?php endif; ?>
		<?php if ($page*$records_per_page < $num_contacts): ?>
		<a href="read.php?page=<?=$page+1?>"><i class="fas fa-angle-double-right fa-sm"></i></a>
		<?php endif; ?>
	</div>
</div>

<?=template_footer()?>

در این کد ابتدا template_header را صدا زده ام تا در اول کار header برنامه را داشته باشیم. سپس یک جدول ساده HTML را داریم که از طریق یک حلقه foreach رکوردهای مورد نظر ما را درون این جدول چاپ می کند. در هر ردیف از جدول، دو دکمه <a> نیز داریم که برایش ویرایش و حذف هر کدام از افراد موجود در contacts می باشد:

<a href="update.php?id=<?=$contact['id']?>" class="edit"><i class="fas fa-pen fa-xs"></i></a>
<a href="delete.php?id=<?=$contact['id']?>" class="trash"><i class="fas fa-trash fa-xs"></i></a>

همانطور که می بینید id هر فرد را برای href قرار داده ایم تا با استفاده از متد GET آن را دریافت و حذف یا ویرایش کنیم. در انتهای این فایل نیز یک pagination ساده را پیاده سازی کرده ام که منطق واضحی دارد. این pagination تنها دو شرط if است. اولین شرط if می گوید اگر از صفحه اول رد شده باشیم دکمه مراجعه به صفحه قبل را نشان بده:

<?php if ($page > 1): ?>
	<a href="read.php?page=<?=$page-1?>"><i class="fas fa-angle-double-left fa-sm"></i></a>
<?php endif; ?>

Page همان صفحه ای است که در آن قرار داریم بنابراین اگر از 1 بزرگتر باشد یعنی در صفحه 2 یا 3 یا 100 و ... هستیم که همگی از 1 بیشتر هستند. قاعدتا در این حالت دکمه «برگشت» را می خواهیم اما این دکمه نباید در خود صفحه 1 نمایش داده شود چرا که صفحه 1 اولین صفحه است و قبل از آن اصلا صفحه ای وجود ندارد که بخواهیم به آن برگردیم. اگر کاربر روی این دکمه کلیک کند فایل read.php فعال می شود و به صفحه مورد نظر می رود (یعنی صفحه فعلی منهای 1 تا بشود صفحه قبلی).

شرط if دوم می گوید تا زمانی که در صفحه آخر نیستیم دکمه مراجعه به صفحه بعد را نشان بده:

<?php if ($page*$records_per_page < $num_contacts): ?>
	<a href="read.php?page=<?=$page+1?>"><i class="fas fa-angle-double-right fa-sm"></i></a>
<?php endif; ?>

اگر ما صفحه فعلی را در تعداد رکورد در هر صفحه (که 5 بود) ضرب کنیم، موقعیت خود را در رکوردها پیدا می کنیم. به طور مثال اگر من در صفحه 3 باشم باید 3 را ضرب در 5 کنم (این مقدار را خودمان تعیین کردیم تا در هر صفحه فقط 5 رکورد نمایش داده شود) که می شود 15. یعنی آخرین رکوردی که در صفحه فعلی نمایش داده شده است رکورد 15 است. حالا اگر کل جدول نیز 15 رکورد داشته باشد چطور؟ آیا می توانیم به صفحه بعد برویم؟ قطعا خیر! بنابراین گفته ایم تا زمانی که آخرین رکورد نمایش داده شده از تعداد کل رکوردها کمتر بود (هنوز صفحه بعدی وجود داشت) دکمه «بعدی» را نمایش بده. کلاس هایی که روی این دکمه ها مشاهده می کنید مربوط به کتابخانه font awesome است و ربطی به کد PHP ما ندارد. اگر با نحوه انجام pagination آشنایی ندارید می توانید به این مقاله مراجعه کنید.

همچنین همانطور که گفتم برای آیکون های حذف و ویرایش از کتابخانه Font Awesome استفاده کرده ایم. حالا اگر بخواهیم کدهایی را که نوشته ایم در مرورگر مشاهده کنیم باید به آدرس http://localhost/phpcrud/read.php در مرورگر برویم:

ظاهر جدول (فایل read.php)
ظاهر جدول (فایل read.php)

همانطور که می بینید برنامه ما ظاهر خوبی دارد. تنها مشکل این جدول این است که دکمه های ویرایش و حذف کار نمی کنند چرا که هنوز فایل های update.php و delete.php را نساخته ایم. در قسمت بعد که قسمت آخر از این مقالات آموزشی است، آن فایل ها را نیز تکمیل خواهیم کرد.

ساخت فایل Create.php

حالا که فایل read.php را با موفقیت تکمیل کردیم نوبت به فایل create.php می رسد. صفحه create مسئول ساخت رکوردهای جدید و ثبت آن ها در جدول Contacts است. ما برای انجام این کار ابتدا کد زیر را در این فایل قرار می دهیم:

<?php
include 'functions.php';
$pdo = pdo_connect_mysql();
$msg = '';
// Check if POST data is not empty
if (!empty($_POST)) {
    // Post data not empty insert a new record
    // Set-up the variables that are going to be inserted, we must check if the POST variables exist if not we can default them to blank
    $id = isset($_POST['id']) && !empty($_POST['id']) && $_POST['id'] != 'auto' ? $_POST['id'] : NULL;
    // Check if POST variable "name" exists, if not default the value to blank, basically the same for all variables
    $name = isset($_POST['name']) ? $_POST['name'] : '';
    $email = isset($_POST['email']) ? $_POST['email'] : '';
    $phone = isset($_POST['phone']) ? $_POST['phone'] : '';
    $title = isset($_POST['title']) ? $_POST['title'] : '';
    $created = isset($_POST['created']) ? $_POST['created'] : date('Y-m-d H:i:s');
    // Insert new record into the contacts table
    $stmt = $pdo->prepare('INSERT INTO contacts VALUES (?, ?, ?, ?, ?, ?)');
    $stmt->execute([$id, $name, $email, $phone, $title, $created]);
    // Output message
    $msg = 'Created Successfully!';
}
?>

اولین قسمت کار وارد کردن فایل توابع و سپس برقراری اتصال با پایگاه داده است. در مرحله بعد با یک شرط ساده از ثبت شدن فرم (خالی نبودن POST) اطمینان حاصل کرده ایم چرا که اگر POST خالی باشد اصلا نیازی به پردازش فرم نیست. پس از آنکه مطمئن شدیم فرم ثبت شده است باید تک تک عناصر فرم را بررسی کنیم.

در مورد فیلد id قرار است مقدار auto را به صورت پیش فرض روی این فیلد قرار دهیم به همین دلیل است که اول چک کرده ایم که آیدی set شده باشد، دوم چک کرده ایم که خالی نباشد و سپس چک کرده ایم که برابر auto نباشد. اگر تمام شروط صحیح بود از مقدار id استفاده می کنیم و در غیر این صورت null را برای این فیلد ذخیره خواهیم کرد. در مرحله بعد تک تک فیلد ها را چک کرده ایم و اگر در آن ها چیزی وارد نشده باشد مقدارشان را برابر یک رشته خالی در نظر می گیریم، البته به غیر از فیلد created که از تابع Date استفاده کرده است تا تاریخ فعلی را ذخیره کند.

در قسمت بعد از prepared statement ها (البته از نوع positional) استفاده کرده ایم تا از حملات SQL Injection جلوگیری کنیم و نهایتا داده ها را به جدول contacts ارسال کرده ایم. در نهایت نیز متغیری به نام msg تعریف کرده ایم که پیام ثبت موفقیت آمیز را در خود نگه می دارد تا به کاربر اعلام کند. حالا قسمت دوم کدها را نیز پس از کدهای بالا به فایل create.php اضافه کنید:

<?=template_header('Create')?>

<div class="content update">
	<h2>Create Contact</h2>
    <form action="create.php" method="post">
        <label for="id">ID</label>
        <label for="name">Name</label>
        <input type="text" name="id" placeholder="26" value="auto" id="id">
        <input type="text" name="name" placeholder="John Doe" id="name">
        <label for="email">Email</label>
        <label for="phone">Phone</label>
        <input type="text" name="email" placeholder="johndoe@example.com" id="email">
        <input type="text" name="phone" placeholder="2025550143" id="phone">
        <label for="title">Title</label>
        <label for="created">Created</label>
        <input type="text" name="title" placeholder="Employee" id="title">
        <input type="datetime-local" name="created" value="<?=date('Y-m-d\TH:i')?>" id="created">
        <input type="submit" value="Create">
    </form>
    <?php if ($msg): ?>
    <p><?=$msg?></p>
    <?php endif; ?>
</div>

<?=template_footer()?>

در این کدها ابتدا توابع header و footer را صدا زده ایم تا شکل صفحه را از دست ندهیم. سپس فرم ساده HTML ای را ساخته ایم که فیلد های مورد نظر ما مانند نام، ایمیل، شماره تلفن و ... را دارد تا کاربر اطلاعات خود را در آن ها وارد کند. همچنین برای فیلد created از تابع date استفاده کرده ایم:

        <input type="datetime-local" name="created" value="<?=date('Y-m-d\TH:i')?>" id="created">

با این کار زمانی که کاربر صفحه create.php را باز کند، تاریخ آن روز برایش به صورت آماده در این فیلد نمایش داده می شود. در نهایت با یک شرط if چک کرده ایم که اگر متغیر msg ساخته شده بود (فرم با موفقیت ثبت شده بود) محتوای آن را در یک پاراگراف نمایش بده. انتظار دارم بدانید که خصوصیت name روی هر کدام از input ها نام همان مقداری است که در POST دریافت می کنیم. یعنی اگر name یکی از این فیلد ها را zip_code بگذاریم (و از متد POST برای ارسال فرم استفاده کرده باشیم) می توانیم به صورت زیر به آن دسترسی داشته باشیم:

$_POST['zip_code']

حالا می توانید به صفحه http://localhost/phpcrud/create.php بروید. باید چنین صفحه ای برایتان نمایش داده شود:

صفحه ی Create.php
صفحه Create.php

ساخت فایل Update.php

همانطور که از نام این فایل مشخص است، update.php قرار است مسئول ویرایش یا به روز رسانی ردیف های جدول ما در پایگاه داده باشد. این کار بر اساس id فرد (در قالب درخواست GET) انجام خواهد شد. ابتدا کدهای PHP زیر را در این فایل قرار دهید:

<?php
include 'functions.php';
$pdo = pdo_connect_mysql();
$msg = '';
// Check if the contact id exists, for example update.php?id=1 will get the contact with the id of 1
if (isset($_GET['id'])) {
    if (!empty($_POST)) {
        // This part is similar to the create.php, but instead we update a record and not insert
        $id = isset($_POST['id']) ? $_POST['id'] : NULL;
        $name = isset($_POST['name']) ? $_POST['name'] : '';
        $email = isset($_POST['email']) ? $_POST['email'] : '';
        $phone = isset($_POST['phone']) ? $_POST['phone'] : '';
        $title = isset($_POST['title']) ? $_POST['title'] : '';
        $created = isset($_POST['created']) ? $_POST['created'] : date('Y-m-d H:i:s');
        // Update the record
        $stmt = $pdo->prepare('UPDATE contacts SET id = ?, name = ?, email = ?, phone = ?, title = ?, created = ? WHERE id = ?');
        $stmt->execute([$id, $name, $email, $phone, $title, $created, $_GET['id']]);
        $msg = 'Updated Successfully!';
    }
    // Get the contact from the contacts table
    $stmt = $pdo->prepare('SELECT * FROM contacts WHERE id = ?');
    $stmt->execute([$_GET['id']]);
    $contact = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$contact) {
        die ('Contact doesn\'t exist with that ID!');
    }
} else {
    die ('No ID specified!');
}
?>

اگر یادتان باشد در بخش قبل، فایل Read.php را تکمیل کردیم و برای آن یک دکمه ویرایش قرار دادیم:

<a href="update.php?id=<?=$contact['id']?>" class="edit"><i class="fas fa-pen fa-xs"></i></a>

از این کد می فهمیم که قرار است id را به صورت درخواست GET در URL مرورگر داشته باشیم. بنابراین در کدهای بالا (فایل update.php) و در اولین قدم خالی نبودن این پارامتر در URL را بررسی کرده ایم. در قسمت بعد دقیقا مانند فایل Create.php عمل کرده و تک تک فیلد ها را درون متغیر های خاصی قرار می دهیم (در صورتی که خالی بودند از یک رشته خالی استفاده می کنیم) و در نهایت با یک کوئری update آن ردیف خاص از پایگاه داده را به روز رسانی می کنیم. در مرحله آخر برای دریافت ردیف به روز رسانی شده، آن را SELECT کرده ایم و اگر کوئری ما نتیجه ای در بر نداشت die می کنیم (یعنی اجرای اسکریپت را متوقف می کنیم) تا پیام Contact doesn't exist with that ID چاپ شود.

حالا کدهای زیر را به این فایل اضافه کنید:

<?=template_header('Read')?>

<div class="content update">
	<h2>Update Contact #<?=$contact['id']?></h2>
    <form action="update.php?id=<?=$contact['id']?>" method="post">
        <label for="id">ID</label>
        <label for="name">Name</label>
        <input type="text" name="id" placeholder="1" value="<?=$contact['id']?>" id="id">
        <input type="text" name="name" placeholder="John Doe" value="<?=$contact['name']?>" id="name">
        <label for="email">Email</label>
        <label for="phone">Phone</label>
        <input type="text" name="email" placeholder="johndoe@example.com" value="<?=$contact['email']?>" id="email">
        <input type="text" name="phone" placeholder="2025550143" value="<?=$contact['phone']?>" id="phone">
        <label for="title">Title</label>
        <label for="created">Created</label>
        <input type="text" name="title" placeholder="Employee" value="<?=$contact['title']?>" id="title">
        <input type="datetime-local" name="created" value="<?=date('Y-m-d\TH:i', strtotime($contact['created']))?>" id="created">
        <input type="submit" value="Update">
    </form>
    <?php if ($msg): ?>
    <p><?=$msg?></p>
    <?php endif; ?>
</div>

<?=template_footer()?>

از آنجایی که ردیف به روز رسانی شده را دریافت کرده ایم در کد بالا و برای value های آن ها از همان ردیف استفاده کرده ایم تا کاربر اطلاعات خودش را ببیند. این کار باعث بالا رفتن UX برنامه ما می شود. حالا اگر به فایل read.php بروید باید بتوانیم روی دکمه ویرایش کلیک کنیم.

ساخت فایل Delete.php

این فایل نیز وظیفه حذف ردیف خاصی از پایگاه داده را دارد که از طریق پارامتر id درون URL دریافت می شود. البته کاربر باید آن را تایید کند تا از حذف اشتباهی ردیف ها جلوگیری کنیم. ابتدا کدهای زیر را درون فایل delete.php قرار دهید:

<?php
include 'functions.php';
$pdo = pdo_connect_mysql();
$msg = '';
// Check that the contact ID exists
if (isset($_GET['id'])) {
    // Select the record that is going to be deleted
    $stmt = $pdo->prepare('SELECT * FROM contacts WHERE id = ?');
    $stmt->execute([$_GET['id']]);
    $contact = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$contact) {
        die ('Contact doesn\'t exist with that ID!');
    }
    // Make sure the user confirms before deletion
    if (isset($_GET['confirm'])) {
        if ($_GET['confirm'] == 'yes') {
            // User clicked the "Yes" button, delete record
            $stmt = $pdo->prepare('DELETE FROM contacts WHERE id = ?');
            $stmt->execute([$_GET['id']]);
            $msg = 'You have deleted the contact!';
        } else {
            // User clicked the "No" button, redirect them back to the read page
            header('Location: read.php');
            exit;
        }
    }
} else {
    die ('No ID specified!');
}
?>

منطق این کدها تکراری و ساده است. ابتدا چک می کنیم که id در URL مرورگر باشد و سپس آن id را با یک دستور SELECT در پایگاه داده جست و جو می کنیم. اگر چنین id وجود نداشت اجرای اسکریپت را متوقف می کنیم و در غیر این صورت کاربر باید روی کلید تایید کلیک کند تا ما ردیف را از جدول contacts حذف کنیم. اگر کاربر روی دکمه no کلیک کند نیز او را به صفحه read.php برمی گردانیم. حالا قسمت HTML فایل را نیز به آن اضافه کنید:

<?=template_header('Delete')?>

<div class="content delete">
	<h2>Delete Contact #<?=$contact['id']?></h2>
    <?php if ($msg): ?>
    <p><?=$msg?></p>
    <?php else: ?>
	<p>Are you sure you want to delete contact #<?=$contact['id']?>?</p>
    <div class="yesno">
        <a href="delete.php?id=<?=$contact['id']?>&confirm=yes">Yes</a>
        <a href="delete.php?id=<?=$contact['id']?>&confirm=no">No</a>
    </div>
    <?php endif; ?>
</div>

<?=template_footer()?>

این کد نیز از کاربر می پرسد که آیا نسبت به حذف ردیف اطمینان دارد یا خیر. هر کدام از دکمه های yes یا no خودشان یک درخواست GET هستند که از همین طریق آن ها را قبول می کنیم.

صفحه ی delete.php که از کاربر می خواهد عملیات را تایید کند
صفحه delete.php که از کاربر می خواهد عملیات را تایید کند

به همین راحتی برنامه ساده CRUD خود را نوشتیم! امیدوارم این سری آموزشی برای شما لذت بخش بوده و به دانش شما اضافه کرده باشد.


منبع: وب سایت codeshack

نویسنده شوید
دیدگاه‌های شما (1 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

احمد
25 مهر 1400
عالی بود تشکر

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.