Chapterبەش 9 · Lectureوانە 9.8
Building a CRUD Application with PHP and MySQLدروستکردنی بەرنامەیەکی CRUD بە PHP و MySQL
Create, Read, Update and Delete: the five files of the course's user-management application (config, create, view, update, delete), explained line by line, corrected and made secure, with the students' own CRUD projects as find-the-bugs exercises.
دروستکردن، خوێندنەوە، نوێکردنەوە و سڕینەوە: پێنج فایلەکەی بەرنامەی بەڕێوەبردنی بەکارهێنەرانی کۆرسەکە (config، create، view، update، delete)، دێڕ بە دێڕ ڕوونکراوەتەوە، ڕاستکراوەتەوە و سەلامەت کراوە، لەگەڵ پڕۆژەکانی CRUDـی خودی قوتابیان وەک ڕاهێنانی دۆزینەوەی باگ.
What you will learnچی فێر دەبیت
- Explain what CRUD and persistent storage are, and map each operation to SQL, a page and an HTTP method.
- Build config.php, create.php, view.php, update.php and delete.php with MySQLi prepared statements.
- Use POST for every change, Post/Redirect/Get with
header()andexit, and escape all output. - Find and fix bugs and security holes in real student CRUD code.
- ڕوونی بکەیتەوە CRUD و چین و هەر کارێک ببەستیتەوە بە SQL و لاپەڕەیەک و .
- config.php و create.php و view.php و update.php و delete.php بە MySQLi دروست بکەیت.
- POST بۆ هەموو گۆڕانکارییەک، Post/Redirect/Get بە
header()وexitبەکاربهێنیت و هەموو دەرخستەیەک بێزیان بکەیت. - باگ و کونی ئاسایشی لە کۆدی ڕاستەقینەی CRUDـی قوتابیاندا بدۆزیتەوە و چاکیان بکەیت.
What CRUD isCRUD چییە
Lecture 8 (PHP MySQL (CRUD Operation)) and Lab 8 (PHP and MySQL CRUD Operations) build one small application together: a list of users that you can add to, read, edit and delete. The lecture shows the code of config.php and delete.php; the lab shows create.php, view.php and update.php and asks you to write delete in class. This lecture puts the two halves together, explains every line, and turns the code into a version that is safe to put online.
وانەی ٨ (PHP MySQL (CRUD Operation)) و تاقیگەی ٨ (PHP and MySQL CRUD Operations) پێکەوە یەک بەرنامەی بچووک دروست دەکەن: لیستێکی بەکارهێنەران کە دەتوانیت زیادی بکەیت، بیخوێنیتەوە، دەستکاری بکەیت و بیسڕیتەوە. وانەکە کۆدی config.php و delete.php پیشان دەدات؛ تاقیگەکە create.php و view.php و update.php پیشان دەدات و داوات لێدەکات سڕینەوە لە پۆلدا بنووسیت. ئەم وانەیە دوو نیوەکە پێکەوە دادەنێت، هەموو دێڕێک ڕوون دەکاتەوە و کۆدەکە دەکات بە وەشانێک کە بۆ سەر ئینتەرنێت سەلامەت بێت.
Lecture 8's outline lists: what CRUD is, the importance of CRUD in PHP, and how to create the PHP–MySQL database connection. It then also covers the four operation pages, which are all here.
نەخشەی وانەی ٨ ئەمانە دەژمێرێت: CRUD چییە، گرنگیی CRUD لە PHPـدا و چۆنیەتی دروستکردنی PHP و MySQL. پاشان چوار لاپەڕەی کارەکانیش دەگرێتەوە، کە هەموویان لێرەن.
The four basic actions on stored data: Create, Read, Update and Delete. In SQL they are INSERT, SELECT, UPDATE and DELETE.
چوار کارە بنەڕەتییەکەی سەر دراوەی خەزنکراو: دروستکردن (Create)، خوێندنەوە (Read)، نوێکردنەوە (Update) و سڕینەوە (Delete). لە SQLـدا بریتین لە INSERT، SELECT، UPDATE و DELETE.
Lecture 8 defines CRUD as "a computer programming acronym that stands for create, read, update, and delete", the four functions "that are required to develop a persistent storage application". Put simply, they are the four basic actions that database and web applications use to work with data.
وانەی ٨ CRUD وا پێناسە دەکات: «کورتکراوەیەکی بەرنامەسازیی کۆمپیوتەرە بۆ create و read و update و delete»، ئەو چوار کارەی «کە بۆ گەشەپێدانی بەرنامەیەکی پێویستن». بە سادەیی، ئەوان چوار کارە بنەڕەتییەکەن کە بەرنامەکانی بنکەدراوە و وێب بۆ کارکردن لەگەڵ دراوە بەکاریان دەهێنن.
| CRUD | SQL commandفەرمانی SQL | Page in our appلاپەڕە لە بەرنامەکەماندا | HTTP method |
|---|---|---|---|
| Create: add a new rowCreate (دروستکردن): ڕیزێکی نوێ زیاد بکە | INSERT | create.php | POST (the form)POST (فۆرمەکە) |
| Read: show rowsRead (خوێندنەوە): ڕیزەکان پیشان بدە | SELECT | view.php | GET |
| Update: change a rowUpdate (نوێکردنەوە): ڕیزێک بگۆڕە | UPDATE | update.php | GET to load the form, POST to saveGET بۆ هێنانی فۆرمەکە، POST بۆ پاشەکەوتکردن |
| Delete: remove a rowDelete (سڕینەوە): ڕیزێک لاببە | DELETE | delete.php | POST (a button, never a plain link)POST (دوگمەیەک، هەرگیز بەستەرێکی ئاسایی نا) |
Think of a teacher's class register. Writing a new student's name is create. Reading the list during roll call is read. Correcting a misspelled name is update. Crossing out a student who moved to another school is delete.
تۆماری پۆلی مامۆستایەک بهێنە بەرچاو. نووسینی ناوی قوتابییەکی نوێ createـە. خوێندنەوەی لیستەکە لە کاتی ئامادەبووندا readـە. ڕاستکردنەوەی ناوێکی بە هەڵە نووسراو updateـە. هێڵکێشان بەسەر ناوی قوتابییەکدا کە گواستراوەتەوە بۆ قوتابخانەیەکی تر deleteـە.
Where the analogy stops workingسنووری ئەم نموونەیە
A paper register has no rules about who may write in it. In a web application you must add those rules yourself.
تۆمارێکی کاغەزی هیچ یاسایەکی نییە دەربارەی ئەوەی کێ دەتوانێت تێیدا بنووسێت. لە بەرنامەیەکی وێبدا دەبێت خۆت ئەو یاسایانە زیاد بکەیت.
What CRUD isCRUD چییە
Persistent storageخەزنکردنی هەمیشەیی
Storage that keeps data after the program stops or the computer turns off, such as a or a file. A , by contrast, disappears when the script ends.
ئەو شێوەی خەزنکردنەیە کە دراوەکان دەپارێزێت تەنانەت دوای وەستانی بەرنامەکە یان کوژاندنەوەی کۆمپیوتەرەکە، وەک یان فایل. بە پێچەوانەوە، کاتێک کۆدەکە تەواو دەبێت لە ناو دەچێت.
A persistent storage application keeps data for a long time, even after the application or the computer is shut down or restarted. That is different from temporary storage such as RAM or a cache, which forgets everything when the power goes off. Persistent storage can hold files, images, videos and other kinds of data, and the data stays available even after system failures or other problems.
بەرنامەیەکی دراوە بۆ ماوەیەکی درێژ هەڵدەگرێت، تەنانەت دوای داخستن یان دووبارە هەڵکردنەوەی بەرنامەکە یان کۆمپیوتەرەکە. ئەمە جیاوازە لە خەزنکردنی کاتی وەک RAM یان cache، کە کاتێک کارەبا دەبڕێت هەموو شتێک لەبیر دەکات. دەتوانێت فایل، وێنە، ڤیدیۆ و جۆرەکانی تری دراوە هەڵبگرێت و دراوەکان تەنانەت دوای تێکچوونی سیستەم یان کێشەی تریش بەردەست دەمێننەوە.
- Database systems, such as MySQL and SQLite: data in tables of rows and columns. Our CRUD application uses one.
- File systems: ordinary folders and files on a disk.
- Object storage systems: they keep huge amounts of unstructured data that is written once and read once or many times, for example the photos and videos on Facebook, the songs on Spotify, and the files in online services such as Dropbox.
- سیستەمەکانی ، وەک MySQL و SQLite: دراوە لە خشتەی ڕیز و ستووندا. بەرنامە CRUDـەکەمان یەکێکیان بەکاردەهێنێت.
- : فۆڵدەر و فایلی ئاسایی لەسەر دیسکێک.
- سیستەمەکانی هەڵگرتنی ئۆبجێکت (object storage): بڕێکی زۆر لە دراوەی بێڕێکخستن هەڵدەگرن کە یەک جار دەنووسرێت و یەک جار یان زۆر جار دەخوێنرێتەوە، بۆ نموونە وێنە و ڤیدیۆکانی Facebook، گۆرانییەکانی Spotify و فایلەکانی خزمەتگوزارییە ئۆنلاینەکانی وەک Dropbox.
What CRUD isCRUD چییە
Why CRUD mattersبۆچی CRUD گرنگە
CRUD matters in PHP because it lets web applications work with data. Almost every web application is built from these four operations on data stored in a database or another persistent store. Lecture 8 gives one more reason, which it calls operation security control:
CRUD لە PHPـدا گرنگە چونکە ڕێگە بە دەدات کار لەگەڵ دراوەدا بکەن. نزیکەی هەموو لەم چوار کارە دروستکراوە لەسەر دراوەیەک کە لە بنکەدراوە یان شوێنێکی تری هەمیشەییدا هەڵگیراوە. وانەی ٨ هۆکارێکی تریش دەدات، کە ناوی دەنێت کۆنترۆڵی ئاسایشی کارەکان:
- Fine-grained access control: because each operation is separate, you can allow one and forbid another, for example by the user's role or permission level.
- Example 1, user data: users may read and write their own data, but may not change other users' data without proper .
- Example 2, a finance application: only authorised users may see sensitive data such as income. Limiting who can read, write or delete keeps the data secure and correct (its integrity).
- کۆنترۆڵی ورد بەسەر دەستڕاگەیشتندا: لەبەر ئەوەی هەر کارێک جیایە، دەتوانیت ڕێگە بە یەکێکیان بدەیت و ئەوی تر قەدەغە بکەیت، بۆ نموونە بەپێی ڕۆڵ یان ئاستی مۆڵەتی بەکارهێنەر.
- نموونەی ١، دراوەی بەکارهێنەر: بەکارهێنەران دەتوانن دراوەی خۆیان بخوێننەوە و بنووسن، بەڵام ناتوانن بەبێ دروست دراوەی بەکارهێنەرانی تر بگۆڕن.
- نموونەی ٢، بەرنامەیەکی دارایی: تەنها بەکارهێنەرانی ڕێگەپێدراو دەتوانن دراوەی هەستیار وەک داهات ببینن. سنووردارکردنی ئەوەی کێ دەتوانێت بخوێنێتەوە، بنووسێت یان بسڕێتەوە، دراوەکان سەلامەت و دروست دەهێڵێتەوە (تەواوێتیـیان).
The application: five filesبەرنامەکە: پێنج فایل
The application has one table, users, and five PHP files. Every page includes config.php to get the database connection. view.php is the start page; the others are reached from it.
بەرنامەکە یەک خشتەی هەیە، users و پێنج فایلی PHP. هەموو لاپەڕەیەک config.php هاوردە دەکات بۆ وەرگرتنی . view.php لاپەڕەی سەرەتایە؛ ئەوانی تر لێیەوە دەگەیەنرێن.
How data travels between the pages
دراوە چۆن لە نێوان لاپەڕەکاندا دەڕوات
- Form fields →
$_POST: the Signup form and the update form send their fields with POST. - Link query strings →
$_GET: the Edit linkupdate.php?id=3gives$_GET['id']. - A hidden input carries the id from the page that shows the form (GET) to the request that saves it (POST).
- خانەکانی فۆرم ←
$_POST: فۆرمی خۆتۆمارکردن و فۆرمی نوێکردنەوە خانەکانیان بە POST دەنێرن. - بەستەر ←
$_GET: بەستەری Editـیupdate.php?id=3نرخی$_GET['id']دەدات. - خانەیەکی شاراوە id دەگوازێتەوە لەو لاپەڕەیەی فۆرمەکە پیشان دەدات (GET) بۆ ئەو داواکارییەی پاشەکەوتی دەکات (POST).
The steps, as the course summary lists them
هەنگاوەکان، وەک پوختەی کۆرسەکە دەیانژمێرێت
- Establish a database connection (
config.php). - The first operation, create: add new records to an existing table.
- The second operation, read: display the data that already exists in the database.
- The third operation, update: change or modify existing data.
- The last operation, delete: remove an existing entry.
- دروست بکە (
config.php). - یەکەم کار، create: نوێ بۆ خشتەیەکی هەبوو زیاد بکە.
- کاری دووەم، read: ئەو دراوانە پیشان بدە کە پێشتر لە بنکەدراوەکەدا هەن.
- کاری سێیەم، update: دراوەی هەبوو بگۆڕە یان دەستکاری بکە.
- کاری کۆتایی، delete: تۆمارێکی هەبوو لاببە.
The table
خشتەکە
Neither the lecture nor the lab shows how the mydb database and the users table are made. This is a table that fits all the pages (run it in phpMyAdmin's SQL tab):
نە وانەکە و نە تاقیگەکە پیشانی نادەن بنکەدراوەی mydb و خشتەی users چۆن دروست دەکرێن. ئەمە خشتەیەکە کە لەگەڵ هەموو لاپەڕەکان دەگونجێت (لە تابی SQLـی phpMyAdmin جێبەجێی بکە):
users tableخشتەی usersschema.sqlSQLRuns in the databaseلە ناو بنکەدراوە کار دەکاتCREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE mydb;
CREATE TABLE users (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
firstname VARCHAR(50) NOT NULL,
lastname VARCHAR(50) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL, -- holds a password_hash() result, never the real password
gender ENUM('Male', 'Female') NOT NULL
);Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 1
utf8mb4stores every language, including Kurdish.utf8mb4هەموو زمانێک هەڵدەگرێت، لەوانە کوردی.- Lineدێڕی 5
AUTO_INCREMENT PRIMARY KEY: MySQL gives each new row the next id.AUTO_INCREMENT PRIMARY KEY: MySQL ژمارەی دواتر دەداتە هەر ڕیزێکی نوێ.- Lineدێڕی 8
UNIQUE: two users cannot have the same email address.UNIQUE: دوو بەکارهێنەر ناتوانن هەمان ئیمەیڵیان هەبێت.- Lineدێڕی 9
255 characters, because it holds a
password_hash()result, never the password.255 پیت، چونکە ئەنجامی
password_hash()هەڵدەگرێت، هەرگیز خودی تێپەڕەوشەکە نا.
Rules for every page
یاساکان بۆ هەموو لاپەڕەیەک
- Every query that contains input is a .
- Every id is validated as a whole number.
- Passwords are stored with
password_hash()and are never shown or pre-filled. - Every value printed into HTML goes through
htmlspecialchars()(our helpere()). - Anything that changes data uses POST; after it the page redirects with
header()and stops withexit. - Errors are logged; visitors see a short message.
- هەموو پرسێک کە نێوخستەی تێدایە .
- هەموو idـیەک وەک پشتڕاست دەکرێتەوە.
- تێپەڕەوشە بە
password_hash()هەڵدەگیرێت و هەرگیز پیشان نادرێت یان پێشوەختە پڕ ناکرێتەوە. - هەموو نرخێک کە لەناو HTMLـدا چاپ دەکرێت بە
htmlspecialchars()دا تێدەپەڕێت (کردارە یاریدەدەرەکەمانe()). - هەر شتێک دراوە بگۆڕێت POST بەکاردەهێنێت؛ دوای ئەوە لاپەڕەکە بە
header()ئاڕاستە دەکاتەوە و بەexitدەوەستێت. - هەڵەکان تۆمار دەکرێن؛ سەردانیکەران پەیامێکی کورت دەبینن.
The application: five filesبەرنامەکە: پێنج فایل
A student's page map, checkedنەخشەی لاپەڕەکانی قوتابییەک، پشکنراو
One student drew the whole application as a diagram while studying. It has a student table (username, email, password, gender, id) and these boxes, each pointing to a Connection box:
قوتابییەک لە کاتی خوێندندا هەموو بەرنامەکەی وەک وێنەیەک کێشا. خشتەیەکی studentـی تێدایە (username، email، password، gender، id) و ئەم سندوقانە، کە هەر یەکێکیان ئاماژە بە سندوقی Connection دەکات:
- Initial page 1: Profile.php (insertion), a "non-sticky GET form", with the note "there is no ID input in the form of this page".
- Initial page 2: View.php (HTML table only), with two actions: Edit →
update.phpand Delete →delete.php. - update.php (from the Edit action) and delete.php (from the Delete action).
- لاپەڕەی سەرەتا ١: Profile.php (زیادکردن)، «فۆرمێکی GETـی نالکاو»، بە تێبینی «هیچ خانەیەکی ID لە فۆرمی ئەم لاپەڕەیەدا نییە».
- لاپەڕەی سەرەتا ٢: View.php (تەنها خشتەی HTML)، بە دوو کار: Edit ←
update.phpو Delete ←delete.php. - update.php (لە کاری Edit) و delete.php (لە کاری Delete).
$db_connect = mysqli_connect('localhost','root','','db');
if(!$db_connect){
die("Connection Failed: ".mysqli_error($db_connect));
}$query = "SELECT * FROM Table_Name";
$result = mysqli_query($db_connect, $query);
while ($row = mysqli_fetch_assoc($result)) {
echo $row['col1'];
echo $row['col2'];
echo $row['col{n}'];
}href="update.php?id=<?php echo $row['id']; ?>
href="delete.php?id=<?php echo $row['id']; ?>Step 1: the connection file config.phpهەنگاوی ١: فایلی پەیوەندی config.php
"To begin, establish a database connection with your PHP code. The following code functions as a link between the webpage and the database, which will store the data from the webpage. Name the file config.php."
«بۆ دەستپێکردن، بە کۆدی PHPـەکەت دروست بکە. ئەم کۆدەی خوارەوە وەک پردێک کار دەکات لە نێوان لاپەڕەکە و بنکەدراوەکەدا، کە دراوەکانی لاپەڕەکە هەڵدەگرێت. ناوی فایلەکە بنێ config.php.»
<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "mydb";
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
?>Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2-5
Four variables: the server, the MySQL user, its password and the database name.
چوار گۆڕاو: ڕاژەکار، بەکارهێنەری MySQL، تێپەڕەوشەکەی و ناوی بنکەدراوەکە.
- Lineدێڕی 6
new mysqli(...)opens the connection and selectsmydbin one step (object style).new mysqli(...)پەیوەندییەکە دەکاتەوە وmydbهەڵدەبژێرێت بە یەک هەنگاو (شێوازی ئۆبجێکت).- Lineدێڕی 8-10
If the connection failed,
connect_errorholds the reason anddie()stops the script.ئەگەر پەیوەندییەکە شکستی هێنا،
connect_errorهۆکارەکە هەڵدەگرێت وdie()دەسنووسەکە ڕادەگرێت.
The other pages share this one $conn by writing include "config.php"; at the top. The course summary shows the same code with the variable $con and the database myDB.
لاپەڕەکانی تر بە نووسینی include "config.php"; لە سەرەوە هەمان $conn بەکاردەهێنن. پوختەی کۆرسەکە هەمان کۆد بە گۆڕاوی $con و بنکەدراوەی myDB پیشان دەدات.
<?php
// config.php - one shared database connection for every page (mysqli, object-oriented style)
$servername = "localhost";
$username = "root"; // XAMPP default only; a real server needs its own low-privilege user
$password = "";
$dbname = "mydb";
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // errors become exceptions (default since PHP 8.1)
try {
$conn = new mysqli($servername, $username, $password, $dbname);
$conn->set_charset("utf8mb4"); // needed for Kurdish (Arabic-script) text
} catch (mysqli_sql_exception $e) {
error_log($e->getMessage()); // the details go to the server's error log
exit("Database connection failed."); // the visitor sees only a short message
}
// e() = escape a value before printing it into HTML (used by every page)
function e($value): string {
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 8
Make every MySQLi error an exception (already the default since PHP 8.1, but now it is clear).
هەموو هەڵەیەکی MySQLi بکە بە هەڵاوێردە (لە PHP 8.1ـەوە بنەڕەتییە، بەڵام ئێستا ڕوونە).
- Lineدێڕی 11
Kurdish-safe character set.
کۆمەڵە پیتێک کە بۆ کوردی سەلامەتە.
- Lineدێڕی 13-14
Details to the server log, a short message to the visitor.
وردەکاری بۆ لۆگی ڕاژەکار، پەیامێکی کورت بۆ سەردانیکەر.
- Lineدێڕی 18-20
e()escapes a value for HTML. Defining it here means every page can use it.e()نرخێک بۆ HTML بێزیان دەکات. پێناسەکردنی لێرە واتە هەموو لاپەڕەیەک دەتوانێت بەکاری بهێنێت.
Step 2: Create, create.phpهەنگاوی ٢: دروستکردن، create.php
"The first operation in PHP CRUD Operations, Create, is responsible for creating … new records into an existing table. To do that, first, you must write the code for the webpage to create an entry in the database. Name the file as create.php."
«یەکەم کار لە کارەکانی CRUDـی PHP، Create، بەرپرسە لە دروستکردنی … تۆماری نوێ لەناو خشتەیەکی هەبوودا. بۆ ئەوە، سەرەتا دەبێت کۆدی لاپەڕەکە بنووسیت بۆ دروستکردنی تۆمارێک لە بنکەدراوەکەدا. ناوی فایلەکە بنێ create.php.»
The lab gives the page in two parts: a PHP handler and the Signup form. Read them first; problems are listed after them.
تاقیگەکە لاپەڕەکە بە دوو بەش دەدات: بەشێکی PHP بۆ وەرگرتنی فۆرمەکە و فۆرمی خۆتۆمارکردن. سەرەتا بیانخوێنەوە؛ کێشەکان دوای ئەوان نووسراون.
if (isset($_POST['submit'])) {
$first_name = $_POST['firstname'];
$last_name = $_POST['lastname'];
$email = $_POST['email'];
$password = $_POST['password'];
$gender = $_POST['gender'];
$sql = "INSERT INTO `users`(`firstname`, `lastname`, `email`, `password`, `gender`)
VALUES ('$first_name','$last_name','$email','$password','$gender')";
$result = $conn->query($sql);
if ($result == TRUE) {
echo "New record created successfully.";
} else {
echo "Error:" . $sql . "<br>" . $conn->error;
}
$conn->close();
}
?>Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 1
Runs only when the button named
submitwas posted.تەنها کاتێک کار دەکات کە دوگمەی بە ناوی
submitنێردرابێت.- Lineدێڕی 2-6
Copies the five form fields into variables.
پێنج خانەکەی فۆرمەکە دەگوازێتەوە بۆ ناو گۆڕاوەکان.
- Lineدێڕی 7-8
Builds the INSERT by pasting the values into the SQL text (backticks around the names).
INSERTـەکە بە لکاندنی نرخەکان بە دەقی SQLـەوە دروست دەکات (backtick بە دەوری ناوەکاندا).
- Lineدێڕی 9-14
Runs it and prints a success or error message.
جێبەجێی دەکات و پەیامی سەرکەوتن یان هەڵە چاپ دەکات.
- Lineدێڕی 15
Closes the connection.
پەیوەندییەکە دادەخات.
<!DOCTYPE html>
<html>
<body>
<h2>Signup Form</h2>
<form action="" method="POST">
<fieldset>
<legend>Personal information:</legend>
First name:<br />
<input type="text" name="firstname"><br />
Last name:<br />
<input type="text" name="lastname"><br />
Email:<br />
<input type="email" name="email"><br />
Password:<br />
<input type="password" name="password"><br />
Gender:<br>
<input type="radio" name="gender" value="Male">Male
<input type="radio" name="gender" value="Female">Female
<br /><br />
<input type="submit" name="submit" value="submit">
</fieldset>
</form>
</body>
</html>Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 5
action=""sends the form back to the same page;method="POST"matches$_POSTin part 1.action=""فۆرمەکە دەنێرێتەوە بۆ هەمان لاپەڕە؛method="POST"لەگەڵ$_POSTی بەشی ١ دەگونجێت.- Lineدێڕی 6-7
<fieldset>and<legend>group the fields under "Personal information:".<fieldset>و<legend>خانەکان لەژێر «Personal information:»دا کۆدەکەنەوە.- Lineدێڕی 21-22
Both radio buttons share
name="gender", so only one value is sent.هەردوو
name="gender"یان هاوبەشە، بۆیە تەنها یەک نرخ دەنێردرێت.- Lineدێڕی 24
Each input's
namebecomes a$_POSTkey: firstname, lastname, email, password, gender, submit.nameـی هەر خانەیەک دەبێت بە کلیلێکی$_POST: firstname، lastname، email، password، gender، submit.
Step 2: Create, create.phpهەنگاوی ٢: دروستکردن، create.php
The secure create.phpcreate.phpـی سەلامەت
<?php
require "config.php";
$message = "";
if (isset($_POST['submit'])) {
$first_name = trim($_POST['firstname'] ?? '');
$last_name = trim($_POST['lastname'] ?? '');
$email = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
$gender = $_POST['gender'] ?? '';
// 1. Validate on the server
if ($first_name === '' || $last_name === '' || mb_strlen($password) < 8
|| filter_var($email, FILTER_VALIDATE_EMAIL) === false
|| !in_array($gender, ['Male', 'Female'], true)) {
$message = "Please fill in every field correctly (password: at least 8 characters).";
} else {
// 2. Is the email address already used?
$stmt = $conn->prepare("SELECT id FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
if ($stmt->get_result()->num_rows > 0) {
$message = "This email address is already registered.";
} else {
// 3. Insert with a prepared statement; store a hash, never the password
$hash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $conn->prepare(
"INSERT INTO users (firstname, lastname, email, password, gender) VALUES (?, ?, ?, ?, ?)"
);
$stmt->bind_param("sssss", $first_name, $last_name, $email, $hash, $gender);
$stmt->execute();
header("Location: view.php"); // Post/Redirect/Get
exit; // stop here: nothing below may run
}
}
}
?>
<!DOCTYPE html>
<html>
<body>
<h2>Signup Form</h2>
<?php if ($message !== ""): ?>
<p style="color: red;"><?= e($message) ?></p>
<?php endif; ?>
<form action="" method="POST">
<fieldset>
<legend>Personal information:</legend>
<label for="firstname">First name:</label><br>
<input type="text" id="firstname" name="firstname" value="<?= e($_POST['firstname'] ?? '') ?>" required><br>
<label for="lastname">Last name:</label><br>
<input type="text" id="lastname" name="lastname" value="<?= e($_POST['lastname'] ?? '') ?>" required><br>
<label for="email">Email:</label><br>
<input type="email" id="email" name="email" value="<?= e($_POST['email'] ?? '') ?>" required><br>
<label for="password">Password:</label><br>
<input type="password" id="password" name="password" minlength="8" required><br>
Gender:<br>
<label><input type="radio" name="gender" value="Male" <?= ($_POST['gender'] ?? '') === 'Male' ? 'checked' : '' ?> required>Male</label>
<label><input type="radio" name="gender" value="Female" <?= ($_POST['gender'] ?? '') === 'Female' ? 'checked' : '' ?>>Female</label>
<br><br>
<input type="submit" name="submit" value="submit">
</fieldset>
</form>
<p><a href="view.php">See all users</a></p>
</body>
</html>HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<!DOCTYPE html>
<html>
<body>
<h2>Signup Form</h2>
<p style="color: red;">Please fill in every field correctly (password: at least 8 characters).</p>
<form action="" method="POST">
<fieldset>
<legend>Personal information:</legend>
<label for="firstname">First name:</label><br>
<input type="text" id="firstname" name="firstname" value="Karwan" required><br>
<label for="lastname">Last name:</label><br>
<input type="text" id="lastname" name="lastname" value="Ali" required><br>
<label for="email">Email:</label><br>
<input type="email" id="email" name="email" value="karwan@" required><br>
<label for="password">Password:</label><br>
<input type="password" id="password" name="password" minlength="8" required><br>
Gender:<br>
<label><input type="radio" name="gender" value="Male" required>Male</label>
<label><input type="radio" name="gender" value="Female" >Female</label>
<br><br>
<input type="submit" name="submit" value="submit">
</fieldset>
</form>
<p><a href="view.php">See all users</a></p>
</body>
</html>With valid data the page sends nothing at all: it only redirects to view.php. Tested with Aram, O'Brien, a Kurdish name and a <script> name: all four were saved, each with a $2y$ hash; a second account with the same email got "This email address is already registered."
بە دراوەی دروست لاپەڕەکە هیچ شتێک نانێرێت: تەنها ئاڕاستە دەکاتەوە بۆ view.php. بە Aram، O'Brien، ناوێکی کوردی و ناوێکی <script> تاقیکرایەوە: هەر چواریان پاشەکەوت کران، هەر یەکەیان بە هاشێکی $2y$؛ هەژمارێکی دووەم بە هەمان ئیمەیڵ پەیامی «This email address is already registered.»ـی وەرگرت.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 6-10
?? ''avoids warnings when a field is missing;trim()removes spaces at both ends.?? ''ڕێگە لە ئاگادارکردنەوە دەگرێت کاتێک خانەیەک نەبێت؛trim()بۆشایی هەردوو سەر لادەبات.- Lineدێڕی 13-15
Server-side validation: names not empty, password at least 8 characters, a real email, gender from an allow-list.
: ناو بەتاڵ نەبێت، تێپەڕەوشە لانیکەم 8 پیت، ئیمەیڵێکی ڕاست، ڕەگەز لە .
- Lineدێڕی 19-23
A prepared SELECT checks whether the email is already used (the column is
UNIQUE).SELECTێکی ئامادەکراو دەپشکنێت ئایا ئیمەیڵەکە پێشتر بەکارهاتووە (ستوونەکە
UNIQUEـە).- Lineدێڕی 26-31
Hash the password, then a prepared INSERT with five
s(string) values.تێپەڕەوشەکە هاش بکە، پاشان INSERTێکی ئامادەکراو بە پێنج نرخی
s(زنجیرەنووسە).- Lineدێڕی 33-34
Post/Redirect/Get: go to the list and stop.
Post/Redirect/Get: بڕۆ بۆ لیستەکە و بوەستە.
- Lineدێڕی 44
The message is escaped like everything else that is printed.
پەیامەکە وەک هەموو شتێکی تری چاپکراو بێزیان دەکرێت.
- Lineدێڕی 50
Sticky value: after an error the user does not have to type it again. The password is never sticky.
نرخی لکاو: دوای هەڵەیەک بەکارهێنەر پێویست ناکات دووبارە بینووسێتەوە. تێپەڕەوشە هەرگیز لکاو نییە.
Step 2: Create, create.phpهەنگاوی ٢: دروستکردن، create.php
Post/Redirect/GetPost/Redirect/Get: پاشەکەوت، ئاڕاستەکردنەوە، هێنان
After a successful POST, the page does not print "New record created successfully.". It sends a with header("Location: view.php") and stops with exit. The browser then loads the list with a normal GET. If the user presses refresh, only that harmless GET is repeated.
دوای POSTێکی سەرکەوتوو، لاپەڕەکە «New record created successfully.» چاپ ناکات. بە header("Location: view.php") دەنێرێت و بە exit دەوەستێت. پاشان تۆڕگەڕەکە لیستەکە بە GETێکی ئاسایی دەهێنێت. ئەگەر بەکارهێنەر لاپەڕەکە نوێ بکاتەوە، تەنها ئەو GETـە بێزیانە دووبارە دەبێتەوە.
Step 3: Read, view.phpهەنگاوی ٣: خوێندنەوە، view.php
"The second operation, just as the name suggests, 'Read' is used to display or read the data that is already available in the database. To perform the operation, you need to create a page that displays the records from the table 'users'. Now, name the page as view.php."
«کاری دووەم، وەک لە ناوەکەیدا دیارە، 'Read' بۆ پیشاندان یان خوێندنەوەی ئەو دراوانە بەکاردێت کە پێشتر لە بنکەدراوەکەدا هەن. بۆ ئەنجامدانی ئەم کارە، پێویستە لاپەڕەیەک دروست بکەیت کە تۆمارەکانی خشتەی 'users' پیشان بدات. ئێستا ناوی لاپەڕەکە بنێ view.php.»
<?php
include "config.php";
$sql = "SELECT * FROM users";
$result = $conn->query($sql);
?>Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 3
Selects every column of every row of
users.هەموو ستوونەکانی هەموو ڕیزەکانی
usersهەڵدەبژێرێت.- Lineدێڕی 4
$resultis amysqli_resultobject; the HTML below loops over it.$resultئۆبجێکتێکیmysqli_resultـە؛ HTMLـەکەی خوارەوە بەسەریدا دەخولێتەوە.
<!DOCTYPE html>
<html>
<head>
<title>View Page</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h2>users</h2>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Gender</th>
<th>Action</th>
</tr>
</thead>
<tbody>Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 5
Loads the Bootstrap 3 stylesheet from a CDN.
شێوازنامەی Bootstrap 3 لە CDNـێکەوە دەهێنێت.
- Lineدێڕی 8-10
class="container"centres the page;class="table"styles the table.class="container"لاپەڕەکە دەخاتە ناوەڕاست؛class="table"شێوە دەداتە خشتەکە.- Lineدێڕی 13-18
Six column headings: ID, First Name, Last Name, Email, Gender, Action.
شەش سەردێڕی ستوون: ID، First Name، Last Name، Email، Gender، Action.
<?php
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
?>
<tr>
<td><?php echo $row['id']; ?></td>
<td><?php echo $row['firstname']; ?></td>
<td> <?php echo $row['lastname']; ?></td>
<td> <?php echo $row['email']; ?> </td>
<td><?php echo $row['gender']; ?></td>
<td><a class="btn btn-info" href="update.php?id=<?php echo $row['id']; ?>">Edit</a> <a
class="btn btn-danger" href="delete.php?id=<?php echo $row['id']; ?>">Delete</a></td>
</tr>
<?php }}?>
</tbody>
</table>
</div>
</body>
</html>Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
num_rowsis the number of rows returned. Nothing is printed if it is 0.num_rowsژمارەی ئەو ڕیزانەیە کە گەڕاونەتەوە. ئەگەر 0 بێت هیچ چاپ ناکرێت.- Lineدێڕی 3
fetch_assoc()returns one row as an associative array each time, until there are no rows left.fetch_assoc()هەر جارە یەک ڕیز وەک دەگەڕێنێتەوە، تا هیچ ڕیزێک نەمێنێت.- Lineدێڕی 4-10
PHP is closed with
?>so that plain HTML can be written; small<?php echo ... ?>islands print each column.PHP بە
?>دادەخرێت بۆ ئەوەی HTMLـی ئاسایی بنووسرێت؛ دوورگەی بچووکی<?php echo ... ?>هەر ستوونێک چاپ دەکەن.- Lineدێڕی 11-12
Two Bootstrap buttons: Edit (
btn btn-info) →update.php?id=N, Delete (btn btn-danger) →delete.php?id=N, separated by .دوو دوگمەی Bootstrap: Edit (
btn btn-info) ←update.php?id=N، Delete (btn btn-danger) ←delete.php?id=N، کە بە جیاکراونەتەوە.- Lineدێڕی 14
}}closes thewhileand then theif.}}یەکەمwhileو پاشانifدادەخات.
This mixing of PHP and HTML is the key idea of the Read page: PHP repeats a block of HTML once for every row.
ئەم تێکەڵکردنەی PHP و HTML بیرۆکەی سەرەکیی لاپەڕەی Readـە: PHP بلۆکێکی HTML بۆ هەر ڕیزێک جارێک دووبارە دەکاتەوە.
Step 3: Read, view.phpهەنگاوی ٣: خوێندنەوە، view.php
The secure view.phpview.phpـی سەلامەت
<?php
require "config.php";
// No user input in this query, so a plain query() is safe here
$result = $conn->query("SELECT id, firstname, lastname, email, gender FROM users ORDER BY id");
?>
<!DOCTYPE html>
<html>
<head>
<title>View Page</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h2>users</h2>
<p><a class="btn btn-primary" href="create.php">Add a user</a></p>
<table class="table">
<thead>
<tr><th>ID</th><th>First Name</th><th>Last Name</th><th>Email</th><th>Gender</th><th>Action</th></tr>
</thead>
<tbody>
<?php if ($result->num_rows === 0): ?>
<tr><td colspan="6">No users yet.</td></tr>
<?php endif; ?>
<?php while ($row = $result->fetch_assoc()): ?>
<tr>
<td><?= e($row['id']) ?></td>
<td><?= e($row['firstname']) ?></td>
<td><?= e($row['lastname']) ?></td>
<td><?= e($row['email']) ?></td>
<td><?= e($row['gender']) ?></td>
<td>
<a class="btn btn-info" href="update.php?id=<?= e($row['id']) ?>">Edit</a>
<form action="delete.php" method="post" style="display:inline"
onsubmit="return confirm('Delete this user?');">
<input type="hidden" name="id" value="<?= e($row['id']) ?>">
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
</body>
</html>HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<!DOCTYPE html>
<html>
<head>
<title>View Page</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h2>users</h2>
<p><a class="btn btn-primary" href="create.php">Add a user</a></p>
<table class="table">
<thead>
<tr><th>ID</th><th>First Name</th><th>Last Name</th><th>Email</th><th>Gender</th><th>Action</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Aram</td>
<td>Ahmed</td>
<td>aram@example.com</td>
<td>Male</td>
<td>
<a class="btn btn-info" href="update.php?id=1">Edit</a>
<form action="delete.php" method="post" style="display:inline"
onsubmit="return confirm('Delete this user?');">
<input type="hidden" name="id" value="1">
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</td>
</tr>
<tr>
<td>2</td>
<td>Sara</td>
<td>O'Brien</td>
<td>sara@example.com</td>
<td>Female</td>
<td>
<a class="btn btn-info" href="update.php?id=2">Edit</a>
<form action="delete.php" method="post" style="display:inline"
onsubmit="return confirm('Delete this user?');">
<input type="hidden" name="id" value="2">
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</td>
</tr>
<tr>
<td>3</td>
<td>شیلان</td>
<td>کەریم</td>
<td>shilan@example.com</td>
<td>Female</td>
<td>
<a class="btn btn-info" href="update.php?id=3">Edit</a>
<form action="delete.php" method="post" style="display:inline"
onsubmit="return confirm('Delete this user?');">
<input type="hidden" name="id" value="3">
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</td>
</tr>
<tr>
<td>4</td>
<td><script>alert(1)</script></td>
<td>Test</td>
<td>x@example.com</td>
<td>Male</td>
<td>
<a class="btn btn-info" href="update.php?id=4">Edit</a>
<form action="delete.php" method="post" style="display:inline"
onsubmit="return confirm('Delete this user?');">
<input type="hidden" name="id" value="4">
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>The <script> name arrives as <script>…, so the browser shows it as text. O'Brien becomes O'Brien in the source and O'Brien on screen. The Kurdish name is unchanged.
ناوی <script> وەک <script>… دەگات، بۆیە تۆڕگەڕ وەک دەق پیشانی دەدات. O'Brien لە سەرچاوەدا دەبێت بە O'Brien و لەسەر شاشە O'Brien. ناوە کوردییەکە ناگۆڕێت.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 5
Only the columns we show, in a fixed order. No input is used, so
query()is fine.تەنها ئەو ستوونانەی پیشانیان دەدەین، بە ڕیزبەندییەکی جێگیر. هیچ نێوخستەیەک بەکارنەهاتووە، بۆیە
query()باشە.- Lineدێڕی 22-24
A friendly row when the table is empty.
ڕیزێکی دۆستانە کاتێک خشتەکە بەتاڵە.
- Lineدێڕی 25
while (...):…endwhile;is easier to read inside HTML than{…}.while (...):…endwhile;لەناو HTMLـدا لە{…}ئاسانتر دەخوێنرێتەوە.- Lineدێڕی 27-31
<?= ... ?>is short for<?php echo ... ?>; every value goes throughe().<?= ... ?>کورتکراوەی<?php echo ... ?>ـە؛ هەموو نرخێک بەe()دا تێدەپەڕێت.- Lineدێڕی 34-38
Delete is a POST form with a hidden id and a JavaScript
confirm()question.Delete فۆرمێکی POSTـە بە idـیەکی شاراوە و پرسیارێکی
confirm()ـی JavaScript.
Step 4: Update, update.phpهەنگاوی ٤: نوێکردنەوە، update.php
"The third operation, i.e., 'update' is used to change or modify the already existing data present in the database. To do this, you need to create another page to update the details in the database. Here, name the page as update.php."
«کاری سێیەم، واتە 'update'، بۆ گۆڕین یان دەستکاریکردنی ئەو دراوانە بەکاردێت کە پێشتر لە بنکەدراوەکەدا هەن. بۆ ئەمە، پێویستە لاپەڕەیەکی تر دروست بکەیت بۆ نوێکردنەوەی وردەکارییەکان لە بنکەدراوەکەدا. لێرەدا ناوی لاپەڕەکە بنێ update.php.»
update.php does two jobs. Opened from the Edit link (update.php?id=3, a GET request), it loads that user and shows a form already filled with the current values. When that form is submitted (POST, to the same address), it saves the changes. The lab gives it in four parts.
update.php دوو کار دەکات. کاتێک لە بەستەری Edit کراوە دەبێت (update.php?id=3، داواکارییەکی GET)، ئەو بەکارهێنەرە دەهێنێت و فۆرمێک پیشان دەدات کە پێشتر بە نرخە ئێستاکان پڕکراوەتەوە. کاتێک ئەو فۆرمە دەنێردرێت (POST، بۆ هەمان ناونیشان)، گۆڕانکارییەکان پاشەکەوت دەکات. تاقیگەکە بە چوار بەش دەیدات.
<?php
include "config.php";
if (isset($_POST['update'])) {
$firstname = $_POST['firstname'];
$user_id = $_POST['user_id'];
$lastname = $_POST['lastname'];
$email = $_POST['email'];
$password = $_POST['password'];
$gender = $_POST['gender'];
$sql = "UPDATE `users` SET `firstname`='$firstname',`lastname`='$lastname',
`email`='$email',`password`='$password',`gender`='$gender' WHERE `id`='$user_id'";
$result = $conn->query($sql);
if ($result == TRUE) {
echo "Record updated successfully.";
}else{
echo "Error:" . $sql . "<br>" . $conn->error;
} }Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 4
Runs when the submit button named
updatewas posted.کاتێک کار دەکات کە بە ناوی
updateنێردرابێت.- Lineدێڕی 6
user_idcomes from a hidden input in the form.user_idلە خانەیەکی شاراوەی فۆرمەکەوە دێت.- Lineدێڕی 11-12
Builds
UPDATE … SET every column … WHERE id = user_idby pasting values.UPDATE … SET هەموو ستوونێک … WHERE id = user_idبە لکاندنی نرخەکان دروست دەکات.
if (isset($_GET['id'])) {
$user_id = $_GET['id'];
$sql = "SELECT * FROM `users` WHERE `id`='$user_id'";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$first_name = $row['firstname'];
$lastname = $row['lastname'];
$email = $row['email'];
$password = $row['password'];
$gender = $row['gender'];
$id = $row['id'];
}?>Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 1-3
The Edit link supplies
$_GET['id']; the user with that id is selected.بەستەری Edit نرخی
$_GET['id']دەدات؛ بەکارهێنەری خاوەن ئەو idـیە هەڵدەبژێردرێت.- Lineدێڕی 7-14
Each column is copied into a variable for the form.
هەر ستوونێک دەگوازرێتەوە بۆ گۆڕاوێک بۆ فۆرمەکە.
- Lineدێڕی 15
}?>closes only thewhile. The twoifblocks stay open around the form and are closed in part 4.}?>تەنهاwhileدادەخات. دوو بلۆکیifبە دەوری فۆرمەکەدا کراوە دەمێننەوە و لە بەشی ٤دا دادەخرێن.
<h2>User Update Form</h2>
<form action="" method="post">
<fieldset>
<legend>Personal information:</legend>
First name:<br>
<input type="text" name="firstname" value="<?php echo $first_name; ?>">
<input type="hidden" name="user_id" value="<?php echo $id; ?>"><br />
Last name:<br />
<input type="text" name="lastname" value="<?php echo $lastname; ?>"><br />
Email:<br />
<input type="email" name="email" value="<?php echo $email; ?>"><br />
Password:<br />
<input type="password" name="password" value="<?php echo $password; ?>"><br />Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
action=""posts to the same URL, which still contains?id=N, so after saving the GET part runs again and shows the new values under the message.action=""بۆ هەمان URL دەنێرێت، کە هێشتا?id=Nـی تێدایە، بۆیە دوای پاشەکەوتکردن بەشی GET دووبارە کار دەکات و نرخە نوێیەکان لەژێر پەیامەکەدا پیشان دەدات.- Lineدێڕی 7
Each
valueattribute shows the current value from the database.هەر خەسڵەتێکی
valueنرخی ئێستای بنکەدراوەکە پیشان دەدات.- Lineدێڕی 9
The hidden input carries the id back to part 1.
خانە شاراوەکە id دەگەڕێنێتەوە بۆ بەشی ١.
Gender:<br />
<input type="radio" name="gender" value="Male" <?php if($gender == 'Male')
{ echo "checked";} ?> >Male
<input type="radio" name="gender" value="Female" <?php if($gender == 'Female')
{ echo "checked";} ?>>Female
<br /><br />
<input type="submit" value="Update" name="update">
</fieldset>
</form>
</body>
</html>
<?php
} else{
header('Location: view.php');
}}?>Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2-5
The stored gender pre-selects its radio button by printing
checked.ڕەگەزی هەڵگیراو بە چاپکردنی
checkedدوگمەی ڕادیۆکەی خۆی پێشوەختە هەڵدەبژێرێت.- Lineدێڕی 7
The button named
updatetriggers part 1.دوگمەی بە ناوی
updateبەشی ١ کارا دەکات.- Lineدێڕی 14-16
} else {belongs toif (num_rows > 0): no user with that id → back to view.php. The last}closesif (isset($_GET['id'])).} else {سەر بەif (num_rows > 0)ـە: هیچ بەکارهێنەرێک بەو idـیە نییە ← بگەڕێوە بۆ view.php. دوایین}بلۆکیif (isset($_GET['id']))دادەخات.
Step 4: Update, update.phpهەنگاوی ٤: نوێکردنەوە، update.php
The secure update.phpupdate.phpـی سەلامەت
<?php
require "config.php";
$message = "";
// Part 1: the form was submitted (POST) - save the changes
if (isset($_POST['update'])) {
$user_id = filter_var($_POST['user_id'] ?? null, FILTER_VALIDATE_INT);
$firstname = trim($_POST['firstname'] ?? '');
$lastname = trim($_POST['lastname'] ?? '');
$email = trim($_POST['email'] ?? '');
$gender = $_POST['gender'] ?? '';
$password = $_POST['password'] ?? '';
if ($user_id === false || $user_id === null || $firstname === '' || $lastname === ''
|| filter_var($email, FILTER_VALIDATE_EMAIL) === false
|| !in_array($gender, ['Male', 'Female'], true)
|| ($password !== '' && mb_strlen($password) < 8)) {
$message = "Please fill in every field correctly.";
} else {
$stmt = $conn->prepare("UPDATE users SET firstname = ?, lastname = ?, email = ?, gender = ? WHERE id = ?");
$stmt->bind_param("ssssi", $firstname, $lastname, $email, $gender, $user_id);
$stmt->execute();
if ($password !== '') { // change the password only when a new one was typed
$hash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $conn->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->bind_param("si", $hash, $user_id);
$stmt->execute();
}
header("Location: view.php"); // Post/Redirect/Get
exit;
}
}
// Part 2: opened from the Edit link (update.php?id=N) - load that user
$user_id = filter_var($_GET['id'] ?? null, FILTER_VALIDATE_INT);
if ($user_id === false || $user_id === null) {
header("Location: view.php");
exit;
}
$stmt = $conn->prepare("SELECT id, firstname, lastname, email, gender FROM users WHERE id = ?");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$user = $stmt->get_result()->fetch_assoc(); // one row, or null if the id does not exist
if ($user === null) {
header("Location: view.php");
exit;
}
?>
<!DOCTYPE html>
<html>
<body>
<h2>User Update Form</h2>
<?php if ($message !== ""): ?>
<p style="color: red;"><?= e($message) ?></p>
<?php endif; ?>
<form action="" method="post">
<fieldset>
<legend>Personal information:</legend>
<input type="hidden" name="user_id" value="<?= e($user['id']) ?>">
First name:<br>
<input type="text" name="firstname" value="<?= e($user['firstname']) ?>" required><br>
Last name:<br>
<input type="text" name="lastname" value="<?= e($user['lastname']) ?>" required><br>
Email:<br>
<input type="email" name="email" value="<?= e($user['email']) ?>" required><br>
New password (leave empty to keep the old one):<br>
<input type="password" name="password" value=""><br>
Gender:<br>
<input type="radio" name="gender" value="Male" <?= $user['gender'] === 'Male' ? 'checked' : '' ?>>Male
<input type="radio" name="gender" value="Female" <?= $user['gender'] === 'Female' ? 'checked' : '' ?>>Female
<br><br>
<input type="submit" value="Update" name="update">
</fieldset>
</form>
</body>
</html>HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<!DOCTYPE html>
<html>
<body>
<h2>User Update Form</h2>
<form action="" method="post">
<fieldset>
<legend>Personal information:</legend>
<input type="hidden" name="user_id" value="2">
First name:<br>
<input type="text" name="firstname" value="Sara" required><br>
Last name:<br>
<input type="text" name="lastname" value="O'Brien" required><br>
Email:<br>
<input type="email" name="email" value="sara@example.com" required><br>
New password (leave empty to keep the old one):<br>
<input type="password" name="password" value=""><br>
Gender:<br>
<input type="radio" name="gender" value="Male" >Male
<input type="radio" name="gender" value="Female" checked>Female
<br><br>
<input type="submit" value="Update" name="update">
</fieldset>
</form>
</body>
</html>Tests: posting user_id = 0' OR '1'='1 now only shows "Please fill in every field correctly." and no row changes; a valid update redirects; with the password box empty the stored hash stays the same; with a new password it changes.
تاقیکردنەوەکان: ناردنی user_id = 0' OR '1'='1 ئێستا تەنها «Please fill in every field correctly.» پیشان دەدات و هیچ ڕیزێک ناگۆڕێت؛ نوێکردنەوەیەکی دروست ئاڕاستە دەکاتەوە؛ کاتێک بەتاڵە هاشە هەڵگیراوەکە وەک خۆی دەمێنێتەوە؛ بە تێپەڕەوشەیەکی نوێ دەگۆڕێت.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 7
The hidden id is validated like any other input.
idـی شاراوە وەک هەر نێوخستەیەکی تر پشتڕاست دەکرێتەوە.
- Lineدێڕی 14-17
Validate everything; an empty password field is allowed (it means "keep the old one").
هەموو شتێک پشتڕاست بکەرەوە؛ بەتاڵ ڕێگەپێدراوە (واتە «کۆنەکە بهێڵەوە»).
- Lineدێڕی 20-22
A prepared UPDATE;
"ssssi"= four strings and one integer, in the order of the?.UPDATEێکی ئامادەکراو؛
"ssssi"= چوار زنجیرەنووسە و یەک ، بە ڕیزبەندی?ـەکان.- Lineدێڕی 24-29
The password changes only if a new one was typed, and it is hashed.
تێپەڕەوشە تەنها ئەگەر نوێیەک نووسرابێت دەگۆڕێت و هاش دەکرێت.
- Lineدێڕی 30-31
Post/Redirect/Get.
Post/Redirect/Get: ئاڕاستە بکەرەوە و بوەستە.
- Lineدێڕی 36-40
Opened with a bad or missing
?id: back to the list, and stop.بە
?idـی هەڵە یان نەبوو کراوەتەوە: بگەڕێوە بۆ لیستەکە و بوەستە.- Lineدێڕی 44
One row (or
null): nowhileloop is needed.یەک ڕیز (یان
null): هیچ خولێکیwhileپێویست نییە.- Lineدێڕی 68
The password box is always empty.
هەمیشە بەتاڵە.
Step 5: Delete, delete.phpهەنگاوی ٥: سڕینەوە، delete.php
"The last operation of CRUD is Delete and just as the name suggests, it is used to delete an existing entry or table. To perform this operation, you must create a page that would let you choose the data entry that you want to delete from the database. Now, name the file delete.php." The lab adds: "You should implement delete in the class". Lecture 8 gives this code:
«کۆتا کاری CRUD، Deleteـە و وەک لە ناوەکەیدا دیارە، بۆ سڕینەوەی تۆمارێک یان خشتەیەکی هەبوو بەکاردێت. بۆ ئەنجامدانی ئەم کارە، دەبێت لاپەڕەیەک دروست بکەیت کە ڕێگەت پێبدات ئەو تۆمارە هەڵبژێریت کە دەتەوێت لە بنکەدراوەکەدا بیسڕیتەوە. ئێستا ناوی فایلەکە بنێ delete.php.» تاقیگەکە زیاد دەکات: «دەبێت سڕینەوە لە پۆلدا جێبەجێ بکەیت». وانەی ٨ ئەم کۆدە دەدات:
<?php
include "config.php";
if (isset($_GET['id'])) {
$user_id = $_GET['id'];
$sql = "DELETE FROM `users` WHERE `id`='$user_id'";
$result = $conn->query($sql);
if ($result == TRUE) {
echo "Record deleted successfully.";
} else {
echo "Error:" . $sql . "<br>" . $conn->error;
}
}
?>Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
Includes
config.phpto get$conn.config.phpهاوردە دەکات بۆ وەرگرتنی$conn.- Lineدێڕی 4
Runs when the URL has
?id=(from the Delete linkdelete.php?id=Nin view.php).کاتێک کار دەکات کە URLـەکە
?id=ـی تێدا بێت (لە بەستەری Deleteـیdelete.php?id=Nلە view.php).- Lineدێڕی 6-8
Builds
DELETE … WHERE id = that valueand runs it.DELETE … WHERE id = ئەو نرخەدروست دەکات و جێبەجێی دەکات.- Lineدێڕی 10-14
Prints "Record deleted successfully." or "Error:" with the SQL and the MySQL error.
«Record deleted successfully.» یان «Error:» لەگەڵ SQL و هەڵەی MySQL چاپ دەکات.
include 'connection.php';
if (isset($_GET['id']))
{
$user_id = $_GET['id'];
$sql = "delete from users where id='$user_id'";
$result = $con->query($sql);
if ($result == TRUE)
{
echo "Record deleted successfully.";
}
else
{
echo "Error: ".$sql."<br>".$con->connect_error;
}
}Step 5: Delete, delete.phpهەنگاوی ٥: سڕینەوە، delete.php
The secure delete.phpdelete.phpـی سەلامەت
<?php
require "config.php";
// Deleting changes data, so accept it only from a POST form - never from a plain link
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user_id = filter_var($_POST['id'] ?? null, FILTER_VALIDATE_INT);
if ($user_id !== false && $user_id !== null) {
$stmt = $conn->prepare("DELETE FROM users WHERE id = ?");
$stmt->bind_param("i", $user_id);
$stmt->execute();
}
}
header("Location: view.php");
exit;POST id=1' OR '1'='1 -> nothing deleted (4 users left) GET delete.php?id=3 -> nothing deleted (4 users left) POST id=4 -> user 4 deleted, browser sent to view.php
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 5
Only a POST request may delete. Opening
delete.php?id=3as a link does nothing.تەنها داواکارییەکی POST دەتوانێت بسڕێتەوە. کردنەوەی
delete.php?id=3وەک بەستەر هیچ ناکات.- Lineدێڕی 6
The id must be a whole number; the attack text gives
false.id دەبێت بێت؛ دەقی هێرشەکە
falseدەدات.- Lineدێڕی 8-10
A prepared DELETE with one integer.
DELETEێکی ئامادەکراو بە یەک .
- Lineدێڕی 13-14
Always go back to the list, and stop.
هەمیشە بگەڕێوە بۆ لیستەکە و بوەستە.
The Delete button that sends this POST is the small form in the secure view.php: a hidden id input and onsubmit="return confirm('Delete this user?');", which asks the user before the form is sent.
ئەو دوگمەی Deleteـەی ئەم POSTـە دەنێرێت فۆرمە بچووکەکەی ناو view.phpـی سەلامەتە: خانەیەکی شاراوەی id و onsubmit="return confirm('Delete this user?');"، کە پێش ناردنی فۆرمەکە پرسیار لە بەکارهێنەر دەکات.
Learn how another website can trick your browser into deleting or changing data, and how a secret token in the form stops it.
فێر دەبیت چۆن تر دەتوانێت هەڵبخەڵەتێنێت تا بسڕێتەوە یان بیانگۆڕێت و چۆن کۆدێکی نهێنی لەناو ڕێگری لەمە دەکات.
Whole video; GET vs POST at 0:41, the token defence at 6:58.
هەموو ڤیدیۆکە؛ GET بەرامبەر POST لە 0:41 و بەرگری بە کۆدی نهێنی (token) لە 6:58.
The same operations with PDOهەمان کارەکان بە PDO
The course uses MySQLi, but many projects use , which works with MySQL, SQLite and other databases. Here are the four operations as small functions. Because PDO is the same for every database, they were run here on SQLite by changing only the connection line.
کۆرسەکە MySQLi بەکاردەهێنێت، بەڵام زۆر پڕۆژە بەکاردەهێنن، کە لەگەڵ MySQL و SQLite و بنکەدراوەکانی تر کار دەکات. ئەمانە چوار کارەکەن وەک کرداری بچووک. لەبەر ئەوەی PDO بۆ هەموو بنکەدراوەیەک یەکە، لێرە لەسەر SQLite کارپێکران تەنها بە گۆڕینی دێڕی پەیوەندییەکە.
<?php
// config.php - PDO version (works with MySQL; change the DSN for other databases)
$dsn = "mysql:host=localhost;dbname=mydb;charset=utf8mb4";
try {
$pdo = new PDO($dsn, "root", "", [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // the default since PHP 8.0
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // rows come back as associative arrays
PDO::ATTR_EMULATE_PREPARES => false, // real prepared statements in MySQL
]);
} catch (PDOException $e) {
error_log($e->getMessage());
exit("Database connection failed.");
}Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 3
The : driver, host, database and character set in one text.
: درایڤەر، ڕاژەکار، بنکەدراوە و کۆمەڵەی پیت لە یەک دەقدا.
- Lineدێڕی 7
Rows come back as associative arrays, like
fetch_assoc().ڕیزەکان وەک دەگەڕێنەوە، وەک
fetch_assoc().- Lineدێڕی 8
Use real prepared statements in MySQL.
لە MySQLـدا ڕاستەقینە بەکاربهێنە.
<?php
// crud.php - the four CRUD operations as functions (PDO)
function create_user(PDO $pdo, string $first, string $last, string $email, string $password, string $gender): int {
$stmt = $pdo->prepare("INSERT INTO users (firstname, lastname, email, password, gender) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$first, $last, $email, password_hash($password, PASSWORD_DEFAULT), $gender]);
return (int) $pdo->lastInsertId(); // the id the database gave the new row
}
function all_users(PDO $pdo): array {
return $pdo->query("SELECT id, firstname, lastname, email, gender FROM users ORDER BY id")->fetchAll();
}
function find_user(PDO $pdo, int $id): array|false {
$stmt = $pdo->prepare("SELECT id, firstname, lastname, email, gender FROM users WHERE id = :id");
$stmt->execute(['id' => $id]);
return $stmt->fetch(); // false when no row matches
}
function update_user(PDO $pdo, int $id, string $first, string $last, string $email, string $gender): int {
$stmt = $pdo->prepare("UPDATE users SET firstname = ?, lastname = ?, email = ?, gender = ? WHERE id = ?");
$stmt->execute([$first, $last, $email, $gender, $id]);
return $stmt->rowCount(); // number of rows changed
}
function delete_user(PDO $pdo, int $id): int {
$stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->rowCount(); // number of rows deleted
}Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 7
lastInsertId(): the id the database gave the new row.lastInsertId(): ئەو idـیەی بنکەدراوەکە بە ڕیزە نوێیەکەی دا.- Lineدێڕی 15
A named placeholder
:id, filled from the array key'id'.ناودار
:id، کە لە کلیلی'id'ی پڕ دەکرێتەوە.- Lineدێڕی 17
fetch()returnsfalsewhen no row matches.fetch()کاتێک هیچ ڕیزێک نەگونجێتfalseدەگەڕێنێتەوە.- Lineدێڕی 23
rowCount(): how many rows were changed.rowCount(): چەند ڕیز گۆڕدران.- Lineدێڕی 29
rowCount(): how many rows were deleted.rowCount(): چەند ڕیز سڕدرانەوە.
<?php
// Try the same functions on SQLite: only the connection line is different
$pdo = new PDO("sqlite::memory:", null, null, [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
$pdo->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, firstname TEXT NOT NULL, lastname TEXT NOT NULL,
email TEXT NOT NULL UNIQUE, password TEXT NOT NULL, gender TEXT NOT NULL)");
require "crud.php";
$a = create_user($pdo, "Sara", "O'Brien", "sara@example.com", "sara-pass-1", "Female");
$b = create_user($pdo, "دیلان", "کەریم", "dilan@example.com", "dilan-pass-1", "Male");
echo "Created ids: $a, $b\n";
foreach (all_users($pdo) as $u) {
echo $u['id'], ": ", $u['firstname'], " ", $u['lastname'], "\n";
}
echo "Updated rows: ", update_user($pdo, $b, "Dilan", "Karim", "dilan@example.com", "Male"), "\n";
print_r(find_user($pdo, $b));
var_dump(find_user($pdo, 99));
echo "Deleted rows: ", delete_user($pdo, $a), "\n";
echo "Users left: ", count(all_users($pdo)), "\n";Created ids: 1, 2
1: Sara O'Brien
2: دیلان کەریم
Updated rows: 1
Array
(
[id] => 2
[firstname] => Dilan
[lastname] => Karim
[email] => dilan@example.com
[gender] => Male
)
bool(false)
Deleted rows: 1
Users left: 1See named placeholders, then the four CRUD operations written with PDO prepared statements: fetch one row, insert, update and delete.
سەیری ناودار بکە، پاشان چوار کارەکەی کە بە نووسراون: یەک ، زیادکردن، گۆڕین و سڕینەوە.
Watch 26:04–38:43: named placeholders, then fetch, insert, update, delete with PDO.
لە 26:04 تا 38:43 سەیر بکە: ناودار، پاشان ، زیادکردن، گۆڕین و سڕینەوە بە .
Who may do whatکێ دەتوانێت چی بکات
Lecture 8 says CRUD helps to control who can read, change or delete data. In code, that means two checks at the top of every page that shows or changes private data:
وانەی ٨ دەڵێت CRUD یارمەتی کۆنترۆڵکردنی ئەوە دەدات کە کێ دەتوانێت دراوە بخوێنێتەوە، بیگۆڕێت یان بیسڕێتەوە. لە کۆددا، ئەمە واتە دوو پشکنین لە سەرەوەی هەموو لاپەڕەیەک کە دراوەی تایبەت پیشان دەدات یان دەیگۆڕێت:
- : is anybody logged in? If not, redirect to the login page and
exit. - : may this user do this to this row? For example, a user may delete only their own notes, or only an admin may delete users.
- : ئایا کەسێک چووەتە ژوورەوە؟ ئەگەر نا، ئاڕاستە بکەرەوە بۆ لاپەڕەی چوونەژوورەوە و
exit. - : ئایا ئەم بەکارهێنەرە دەتوانێت ئەم کارە بەم ڕیزە بکات؟ بۆ نموونە، بەکارهێنەر تەنها دەتوانێت تێبینییەکانی خۆی بسڕێتەوە، یان تەنها بەڕێوەبەر دەتوانێت بەکارهێنەران بسڕێتەوە.
<?php
session_start(); // the very first line of the page
if (!isset($_SESSION['userId'])) {
header('Location: login.php');
exit; // without exit, the rest of the page is still sent
}The second check can often be written into the SQL itself: add the owner to the WHERE clause. Then a user who changes the id in the form still cannot touch other people's rows.
پشکنینی دووەم زۆرجار دەتوانرێت لەناو خودی SQLـەکەدا بنووسرێت: خاوەنەکە زیاد بکە بۆ . ئەوکات بەکارهێنەرێک کە id لە فۆرمەکەدا دەگۆڕێت هێشتا ناتوانێت دەست لە ڕیزەکانی کەسانی تر بدات.
<?php
$pdo = new PDO("sqlite::memory:");
$pdo->exec("CREATE TABLE notes (id INTEGER PRIMARY KEY, owner_id INTEGER, text TEXT)");
$pdo->exec("INSERT INTO notes (owner_id, text) VALUES (1, 'Aram''s note'), (2, 'Hana''s note')");
function delete_note(PDO $pdo, int $note_id, int $current_user_id): int {
// the extra condition "AND owner_id = ?" means: only your OWN notes
$stmt = $pdo->prepare("DELETE FROM notes WHERE id = ? AND owner_id = ?");
$stmt->execute([$note_id, $current_user_id]);
return $stmt->rowCount();
}
echo "Hana (user 2) deletes note 1: ", delete_note($pdo, 1, 2), " row(s)\n";
echo "Aram (user 1) deletes note 1: ", delete_note($pdo, 1, 1), " row(s)\n";Hana (user 2) deletes note 1: 0 row(s) Aram (user 1) deletes note 1: 1 row(s)
Find the bugs: students' CRUD projectsباگەکان بدۆزەرەوە: پڕۆژەکانی CRUDـی قوتابیان
The course's practical notes contain real CRUD pages written by students while they practised. They work on a teacher's computer, but each one has bugs and security holes. Finding them is the best way to check what you learned. Try each exercise before you open the answer.
تێبینییە پراکتیکییەکانی کۆرسەکە لاپەڕەی ڕاستەقینەی CRUDـیان تێدایە کە قوتابیان لە کاتی ڕاهێناندا نووسیویانە. لەسەر کۆمپیوتەری مامۆستا کار دەکەن، بەڵام هەر یەکێکیان باگ و کونی ئاسایشی تێدایە. دۆزینەوەیان باشترین ڕێگایە بۆ پشکنینی ئەوەی فێری بوویت. هەر ڕاهێنانێک تاقی بکەرەوە پێش ئەوەی وەڵامەکە بکەیتەوە.
Find the bugs: students' CRUD projectsباگەکان بدۆزەرەوە: پڕۆژەکانی CRUDـی قوتابیان
A. The product stock pagesA. لاپەڕەکانی کۆگای بەرهەم
A practice project by two students shows a list of products with their stock. Buy lowers the stock by one (never below zero) and Add raises it by one; both go back to product.php. Read the three files and list every bug and security problem. Then write fixed versions.
پڕۆژەیەکی ڕاهێنانی دوو قوتابی لیستێکی بەرهەمەکان لەگەڵ بڕی کۆگاکەیان پیشان دەدات. Buy کۆگاکە یەکێک کەم دەکاتەوە (هەرگیز لە سفر کەمتر نا) و Add یەکێک زیادی دەکات؛ هەردووکیان دەگەڕێنەوە بۆ product.php. سێ فایلەکە بخوێنەوە و هەموو باگێک و کێشەیەکی ئاسایشی بنووسە. پاشان وەشانی چاککراو بنووسە.
<?php
$connect = mysqli_connect('localhost', 'root', '', 'MyStudy');
if (!$connect) {
die(mysqli_error($connect));
}
?>
<!DOCTYPE html>
<html lang="en">
<body>
<table border="6">
<tr>
<th>ID</th>
<th>PRODUCT NAME</th>
<th>PRICE</th>
<th>QUANTITY</th>
<th>ACTION</th>
</tr>
<tr>
<?php
$selectQuery = mysqli_query($connect, "select * from product");
$QuantityZero = false;
if (mysqli_num_rows($selectQuery)) {
while ($row = mysqli_fetch_assoc($selectQuery)) { ?>
<td><?php echo $row['id']; ?></td>
<td><?php echo $row['name']; ?></td>
<td><?php echo $row['price']."$"; ?></td>
<td><?php if ($row['quantity']<1) {echo "The product not available";}else{echo $row['quantity'];}?></td>
<td><a href="buy.php?id=<?php echo $row['id']; ?>">Buy</a> <a style="color: green;" href="add.php?id=<?php echo $row['id']; ?>">Add</a></td>
</tr>
<?php
}
}
?>
</table>
</body>
</html><!DOCTYPE html>
<html lang="en">
<?php
$connect = mysqli_connect('localhost', 'root', '', 'MyStudy');
if (!$connect) {
die(mysqli_error($connect));
}
?>
<body>
<?php
$id = $_GET['id'];
$updateQuery = mysqli_query($connect, "update product set quantity = (quantity - 1) where id = '$id' and quantity > 0");
if ($updateQuery) {
header('location: product.php');
} else {
die(mysqli_error($connect));
}
?>
</body>
</html><!DOCTYPE html>
<html lang="en">
<?php
$connect = mysqli_connect('localhost', 'root', '', 'MyStudy');
if (!$connect) {
die(mysqli_error($connect));
}
?>
<body>
<?php
$id = $_GET['id'];
$updateQuery = mysqli_query($connect, "update product set quantity = (quantity + 1) where id = '$id'");
if ($updateQuery) {
header('location: product.php');
} else {
die(mysqli_error($connect));
}
?>
</body>
</html>Hintئاماژە
Look at where <tr> opens, at what comes before header(), at the SQL, and at what a plain link can do.
سەیری ئەوە بکە <tr> لە کوێ دەکرێتەوە، چی پێش header() دێت، SQLـەکە و بەستەرێکی ئاسایی دەتوانێت چی بکات.
Show answerوەڵامەکە پیشان بدە
<tr>outside the loop (product.php): it opens once before the loop but closes inside it, so every row after the first has no opening<tr>. The output below shows it.- SQL injection (buy.php, add.php):
where id = '$id'with$id = $_GET['id']. Validate the id and use a prepared statement. - Data changed by plain links (
buy.php?id=…,add.php?id=…): browsers, link previews and other sites can open them. Use POST forms. header()after HTML (buy.php, add.php):<!DOCTYPE html><html lang="en">is sent first, so with output buffering off PHP says "Cannot modify header information - headers already sent". It works on XAMPP only because XAMPP's php.ini buffers output. Put all PHP before any HTML, and addexit;afterheader().- No feedback: when the stock is already 0, the UPDATE "succeeds" with 0 rows changed and the user is told nothing. Check
affected_rows. - Unescaped output of name and price, and
die(mysqli_error($connect))shows database details (and cannot even report a failed connection, because$connectis thenfalse). - Small things:
$QuantityZerois never used; "The product not available" is missing "is"; the price is printed as10$instead of$10; the connection code is copied into every file instead of oneconfig.php; add.php has no upper limit.
<tr>لە دەرەوەی خولەکە (product.php): یەک جار پێش خولەکە دەکرێتەوە بەڵام لەناویدا دادەخرێت، بۆیە هەموو ڕیزێک دوای یەکەم<tr>ـی کردنەوەی نییە. دەرخستەی خوارەوە پیشانی دەدات.- (buy.php، add.php):
where id = '$id'لەگەڵ$id = $_GET['id']. id پشتڕاست بکەرەوە و بەکاربهێنە. - دراوە بە بەستەری ئاسایی دەگۆڕدرێت (
buy.php?id=…،add.php?id=…): تۆڕگەڕ، پێشبینینی بەستەر و تۆڕگەکانی تر دەتوانن بیانکەنەوە. فۆرمی POST بەکاربهێنە. header()دوای HTML (buy.php، add.php): سەرەتا<!DOCTYPE html><html lang="en">دەنێردرێت، بۆیە کاتێک output buffering کوژاوەتەوە PHP دەڵێت «Cannot modify header information - headers already sent». لە XAMPPـدا تەنها کار دەکات چونکە php.iniـی XAMPP دەرخستە کۆدەکاتەوە. هەموو PHP پێش هەر HTMLێک دابنێ و دوایheader()exit;زیاد بکە.- هیچ وەڵامێک نییە: کاتێک کۆگا پێشتر 0ـە، UPDATEـەکە «سەردەکەوێت» بە 0 ڕیزی گۆڕاو و هیچ شتێک بە بەکارهێنەر ناوترێت.
affected_rowsبپشکنە. - دەرخستەی بێزیاننەکراوی ناو و نرخ و
die(mysqli_error($connect))وردەکاری بنکەدراوە پیشان دەدات (و تەنانەت ناتوانێت پەیوەندییەکی شکستخواردوو ڕاپۆرت بکات، چونکە ئەوکات$connectدەبێت بەfalse). - شتی بچووک:
$QuantityZeroهەرگیز بەکارنایەت؛ «The product not available» وشەی «is»ـی کەمە؛ نرخ وەک10$چاپ دەکرێت لە جیاتی$10؛ کۆدی پەیوەندییەکە لە هەموو فایلێکدا کۆپی کراوە لە جیاتی یەکconfig.php؛ add.php هیچ سنوورێکی سەرەوەی نییە.
<tr> bug in miniatureباگی <tr> بە شێوەیەکی بچووکtr-demo.phpPHPRuns on the serverلەسەر ڕاژەکاری وێب کار دەکات<?php
$rows = [['id' => 1, 'name' => 'Notebook'], ['id' => 2, 'name' => 'Pen']];
?>
<table border="1">
<tr>
<?php foreach ($rows as $row) { ?>
<td><?php echo $row['id']; ?></td>
<td><?php echo $row['name']; ?></td>
</tr>
<?php } ?>
</table>HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<table border="1">
<tr>
<td>1</td>
<td>Notebook</td>
</tr>
<td>2</td>
<td>Pen</td>
</tr>
</table>The second row starts with <td> and no <tr>; browsers guess, and the table may break.
ڕیزی دووەم بە <td> دەست پێدەکات بێ <tr>؛ تۆڕگەڕەکان مەزەندە دەکەن و لەوانەیە خشتەکە تێک بچێت.
<?php
require "config.php"; // gives $conn and e()
$messages = ['bought' => 'Thank you! One item was sold.', 'soldout' => 'Sorry, this product is sold out.',
'added' => 'One item was added to the stock.'];
$status = $_GET['status'] ?? '';
$result = $conn->query("SELECT id, name, price, quantity FROM product ORDER BY id");
?>
<!DOCTYPE html>
<html lang="en">
<body>
<?php if (isset($messages[$status])): ?>
<p><?= e($messages[$status]) ?></p>
<?php endif; ?>
<table border="1">
<tr>
<th>ID</th><th>PRODUCT NAME</th><th>PRICE</th><th>QUANTITY</th><th>ACTION</th>
</tr>
<?php while ($row = $result->fetch_assoc()): ?>
<tr>
<td><?= e($row['id']) ?></td>
<td><?= e($row['name']) ?></td>
<td>$<?= e($row['price']) ?></td>
<td><?= $row['quantity'] < 1 ? "Not available" : e($row['quantity']) ?></td>
<td>
<form action="buy.php" method="post" style="display:inline">
<input type="hidden" name="id" value="<?= e($row['id']) ?>">
<button type="submit">Buy</button>
</form>
<form action="add.php" method="post" style="display:inline">
<input type="hidden" name="id" value="<?= e($row['id']) ?>">
<button type="submit" style="color: green;">Add</button>
</form>
</td>
</tr>
<?php endwhile; ?>
</table>
</body>
</html>HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<!DOCTYPE html>
<html lang="en">
<body>
<p>Sorry, this product is sold out.</p>
<table border="1">
<tr>
<th>ID</th><th>PRODUCT NAME</th><th>PRICE</th><th>QUANTITY</th><th>ACTION</th>
</tr>
<tr>
<td>1</td>
<td>Notebook</td>
<td>$2</td>
<td>Not available</td>
<td>
<form action="buy.php" method="post" style="display:inline">
<input type="hidden" name="id" value="1">
<button type="submit">Buy</button>
</form>
<form action="add.php" method="post" style="display:inline">
<input type="hidden" name="id" value="1">
<button type="submit" style="color: green;">Add</button>
</form>
</td>
</tr>
<tr>
<td>2</td>
<td>Pen</td>
<td>$1</td>
<td>5</td>
<td>
<form action="buy.php" method="post" style="display:inline">
<input type="hidden" name="id" value="2">
<button type="submit">Buy</button>
</form>
<form action="add.php" method="post" style="display:inline">
<input type="hidden" name="id" value="2">
<button type="submit" style="color: green;">Add</button>
</form>
</td>
</tr>
<tr>
<td>3</td>
<td><b>Bag</b></td>
<td>$15</td>
<td>Not available</td>
<td>
<form action="buy.php" method="post" style="display:inline">
<input type="hidden" name="id" value="3">
<button type="submit">Buy</button>
</form>
<form action="add.php" method="post" style="display:inline">
<input type="hidden" name="id" value="3">
<button type="submit" style="color: green;">Add</button>
</form>
</td>
</tr>
</table>
</body>
</html>quantity + 1, no AND quantity > 0, and the status "added")buy.phpـی چاککراو (add.php هەمان شتە بە quantity + 1، بێ AND quantity > 0 و دۆخی «added»)buy.phpPHPRuns on the serverلەسەر ڕاژەکاری وێب کار دەکات<?php
require "config.php"; // all PHP work happens BEFORE any HTML is sent
$status = "soldout";
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = filter_var($_POST['id'] ?? null, FILTER_VALIDATE_INT);
if ($id !== false && $id !== null) {
// "AND quantity > 0" keeps the stock from going below zero
$stmt = $conn->prepare("UPDATE product SET quantity = quantity - 1 WHERE id = ? AND quantity > 0");
$stmt->bind_param("i", $id);
$stmt->execute();
if ($stmt->affected_rows === 1) {
$status = "bought";
}
}
}
header("Location: product.php?status=" . $status);
exit;POST id=1 (stock 1) -> stock 0, status=bought POST id=1 again -> stock stays 0, status=soldout GET buy.php?id=2 -> nothing changes POST id=2' OR '1'='1 -> nothing changes
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 4
Pessimistic default: if nothing is bought, the message says sold out.
: ئەگەر هیچ نەکڕدرێت، پەیامەکە دەڵێت کۆگا تەواو بووە.
- Lineدێڕی 9
AND quantity > 0keeps the stock from going below zero.AND quantity > 0ڕێگە دەگرێت کۆگا لە سفر کەمتر بێت.- Lineدێڕی 12
affected_rowsis 1 only when a row really changed.affected_rowsتەنها کاتێک 1ـە کە ڕیزێک بە ڕاستی گۆڕابێت.- Lineدێڕی 17-18
Redirect and stop; nothing was printed before this line.
ئاڕاستە بکەرەوە و بوەستە؛ پێش ئەم دێڕە هیچ چاپ نەکراوە.
Find the bugs: students' CRUD projectsباگەکان بدۆزەرەوە: پڕۆژەکانی CRUDـی قوتابیان
B. The user-management pagesB. لاپەڕەکانی بەڕێوەبردنی بەکارهێنەران
The same students wrote a small user-management area: View.php (only for logged-in users) lists the users with their pictures, update.php changes a username and password, and delete.php removes a user. Passwords are stored with md5(). Find every bug and security problem, then fix the three pages.
هەمان قوتابییان بەشێکی بچووکی بەڕێوەبردنی بەکارهێنەرانیان نووسی: View.php (تەنها بۆ بەکارهێنەرانی چووەژوورەوە) بەکارهێنەران لەگەڵ وێنەکانیان پیشان دەدات، update.php و تێپەڕەوشە دەگۆڕێت و delete.php بەکارهێنەرێک لادەبات. تێپەڕەوشە بە md5() هەڵدەگیرێت. هەموو باگێک و کێشەیەکی ئاسایشی بدۆزەرەوە، پاشان سێ لاپەڕەکە چاک بکە.
<!DOCTYPE html>
<html lang="en">
<body>
<?php
include('config.php');
$id = $_GET['id'];
$selectQuery = mysqli_query($connection,"select * from users where id = '$id'");
if (mysqli_num_rows($selectQuery)){
while($row = mysqli_fetch_assoc($selectQuery)){
$username = $row['username'];
$password = $row['password'];
}
}
if (isset($_POST['Update'])){
$username = $_POST['user'];
$password = md5($_POST['pass']);
$UpdateQuery = mysqli_query($connection,"Update users set username = '$username', password='$password' where id ='$id'");
if ($UpdateQuery){
header('location: View.php');
}
else{
die(mysqli_error($connection));
}
}
?>
<form method="post">
username : <input type="text" name="user" value="<?php echo $username ?> "><br><br>
password : <input type="password" name="pass" value="<?php echo $password ?>"><br><br>
<input type="submit" name="Update" value="Update">
</form>
</body>
</html><!DOCTYPE html>
<html lang="en">
<body>
<?php
include('config.php');
session_start();
if (!isset($_SESSION['userId'])){
header('Location: Register.php');
}
if (!isset($_SESSION['intCount'])){
$_SESSION['intCount']=1;
}
else{
$_SESSION['intCount']++;
}
echo "<h2> Welcome ".$_SESSION['userInput']."</h2>";
echo "<h2> Counter ".$_SESSION['intCount']."....</h2>";
echo "<a href='logout.php'>Logout</a>";
?>
<table border="10" align="center">
<tr>
<th>ID</th>
<th>Usrename</th>
<th>Passowrd</th>
<th>Image</th>
<th>Action</th>
</tr>
<tr>
<?php
$selectionQuery = mysqli_query($connection, "select * from users");
if (mysqli_num_rows($selectionQuery)) {
while ($row = mysqli_fetch_assoc($selectionQuery)) { ?>
<td>
<?php echo $row['id']; ?>
</td>
<td>
<?php echo $row['username']; ?>
</td>
<td>
<?php echo $row['password']; ?>
</td>
<td> <img width="70px" height="70px" src="uploads/<?php echo $row['filename']; ?>"> </td>
<td><a href="update.php?id=<?php echo $row['id']; ?>">Edit</a> <a
href="delete.php?id=<?php echo $row['id']; ?>">delete</a></td>
<!-- … the screenshot ends here … --><?php
include('config.php');
$id = $_GET['id'];
$deleteQuery = mysqli_query($connection,"delete from users where id='$id'");
if ($deleteQuery){
header('Location: View.php');
}
else{
die(mysqli_error($connection));
}
?>Hintئاماژە
Ask for each page: what happens to a visitor who is not logged in? What does the password box contain? What happens if the user clicks Update without typing?
بۆ هەر لاپەڕەیەک بپرسە: چی بەسەر سەردانیکەرێک دێت کە نەچووەتە ژوورەوە؟ چی تێدایە؟ چی ڕوودەدات ئەگەر بەکارهێنەر بێ نووسین کرتە لە Update بکات؟
Show answerوەڵامەکە پیشان بدە
- Missing
exitafter the redirect (View.php):header('Location: Register.php')only adds a header, so the script keeps running and sends the whole users table, passwords included, to a visitor who is not logged in. This is the most serious bug. session_start()andheader()after HTML (View.php, update.php):<!DOCTYPE html>is sent first. With output buffering off, PHP warns "Session cannot be started after headers have already been sent". Start the session on the very first line.- Double hashing (update.php): the password box is pre-filled with the stored md5 hash. If the user clicks Update without typing,
md5()of the hash is saved and the old password stops working (shown below). - A trailing space (update.php):
value="<?php echo $username ?> "adds a space after the name, so every update saves "hana " instead of "hana" (shown below). Whether a later search for'hana'still finds"hana "depends on the column's : the usual MariaDB collations ignore spaces at the end, but MySQL 8's defaultutf8mb4_0900_ai_cidoes not. - SQL injection in all three pages (
'$id','$username'); withdelete.php?id=1' OR '1'='1the DELETE removes every user. - No login check in update.php and delete.php: anyone who knows the URL can edit or delete users. Deleting by a GET link is also unsafe.
- md5 passwords, and View.php shows the password hashes in a table column.
- Unescaped output of usernames, the welcome name and the file name; the deleted user's picture stays in
uploads/; noexitafter the other redirects. - Small things: "Usrename", "Passowrd";
align="center"is obsolete HTML;width="70px"should bewidth="70"; the same<tr>outside the loop as in exercise A; the redirect target isView.phphere butview.phpin the login page, which fails on a case-sensitive Linux server.
exitدوای ئاڕاستەکردنەوە نییە (View.php):header('Location: Register.php')تەنها سەردێڕێک زیاد دەکات، بۆیە دەسنووسەکە بەردەوام دەبێت و هەموو خشتەی بەکارهێنەران، لەگەڵ تێپەڕەوشەکان، دەنێرێت بۆ سەردانیکەرێک کە نەچووەتە ژوورەوە. ئەمە مەترسیدارترین باگە.session_start()وheader()دوای HTML (View.php، update.php): سەرەتا<!DOCTYPE html>دەنێردرێت. کاتێک output buffering کوژاوەتەوە، PHP ئاگادار دەکاتەوە «Session cannot be started after headers have already been sent». سێشن لە یەکەم دێڕدا دەست پێبکە.- دوو جار هاشکردن (update.php): بە هاشی md5ـی هەڵگیراو پێشوەختە پڕ دەکرێتەوە. ئەگەر بەکارهێنەر بێ نووسین کرتە لە Update بکات،
md5()ـی هاشەکە هەڵدەگیرێت و تێپەڕەوشە کۆنەکە ئیتر کار ناکات (لە خوارەوە پیشان دراوە). - بۆشاییەکی کۆتایی (update.php):
value="<?php echo $username ?> "بۆشاییەک دوای ناوەکە زیاد دەکات، بۆیە هەموو نوێکردنەوەیەک «hana » هەڵدەگرێت لە جیاتی «hana» (لە خوارەوە پیشان دراوە). ئایا گەڕانێکی دواتر بۆ'hana'هێشتا"hana "دەدۆزێتەوە یان نا، بەندە بە ستوونەکە: لە MariaDBـدا ئاسایی گوێ بە بۆشایی کۆتایی نادات، بەڵامutf8mb4_0900_ai_ci، کە بنەڕەتیی MySQL 8ـە، گوێی پێدەدات. - لە هەر سێ لاپەڕەکەدا (
'$id'،'$username')؛ بەdelete.php?id=1' OR '1'='1هەموو بەکارهێنەرێک لادەبات. - پشکنینی چوونەژوورەوە نییە لە update.php و delete.php: هەر کەسێک URLـەکە بزانێت دەتوانێت بەکارهێنەران دەستکاری بکات یان بسڕێتەوە. سڕینەوە بە بەستەری GETـیش نەپارێزراوە.
- تێپەڕەوشەی md5 و View.php هاشی تێپەڕەوشەکان لە ستوونێکی خشتەدا پیشان دەدات.
- دەرخستەی بێزیاننەکراوی ناوی بەکارهێنەران، ناوی بەخێرهاتن و ناوی فایل؛ وێنەی بەکارهێنەری سڕاوە لە
uploads/دا دەمێنێتەوە؛ دوای ئاڕاستەکردنەوەکانی ترexitنییە. - شتی بچووک: «Usrename»، «Passowrd»؛
align="center"HTMLـی کۆنە؛width="70px"دەبێتwidth="70"بێت؛ هەمان<tr>ـی دەرەوەی خول وەک ڕاهێنانی A؛ ئامانجی ئاڕاستەکردنەوە لێرەView.phpـە بەڵام لە لاپەڕەی چوونەژوورەوەداview.php، کە لەسەر ڕاژەکارێکی Linux کە شکست دەهێنێت.
<?php
$stored = md5("hana2026"); // what the database holds
echo "Stored: ", $stored, "\n";
// update.php pre-fills the password box with $stored; the user clicks Update without typing
$saved_now = md5($stored); // update.php runs md5() on the hash
echo "Saved after edit: ", $saved_now, "\n";
var_dump(md5("hana2026") === $saved_now); // can the user still log in with hana2026?Stored: e156082d6e2d037a2008943515b4a97a Saved after edit: 9c32fd7d0d591785a4746efbeb8dcae4 bool(false)
<?php
$username = "hana";
?>
<input type="text" name="user" value="<?php echo $username ?> ">HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<input type="text" name="user" value="hana "><?php
session_start(); // the very first thing: before any HTML is sent
if (!isset($_SESSION['userId'])) {
header('Location: login.php');
exit; // without exit, the whole page would still be sent!
}
require "config.php";
$_SESSION['intCount'] = ($_SESSION['intCount'] ?? 0) + 1;
$result = $conn->query("SELECT id, username, filename FROM users ORDER BY id"); // no passwords
?>
<!DOCTYPE html>
<html lang="en">
<body>
<h2>Welcome <?= e($_SESSION['userInput']) ?></h2>
<h2>Counter <?= e($_SESSION['intCount']) ?></h2>
<a href="logout.php">Logout</a>
<table border="1">
<tr><th>ID</th><th>Username</th><th>Image</th><th>Action</th></tr>
<?php while ($row = $result->fetch_assoc()): ?>
<tr>
<td><?= e($row['id']) ?></td>
<td><?= e($row['username']) ?></td>
<td><img width="70" height="70" src="uploads/<?= e(rawurlencode($row['filename'])) ?>" alt=""></td>
<td>
<a href="update.php?id=<?= e($row['id']) ?>">Edit</a>
<form action="delete.php" method="post" style="display:inline"
onsubmit="return confirm('Delete this user?');">
<input type="hidden" name="id" value="<?= e($row['id']) ?>">
<button type="submit">Delete</button>
</form>
</td>
</tr>
<?php endwhile; ?>
</table>
</body>
</html>HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<!DOCTYPE html>
<html lang="en">
<body>
<h2>Welcome hana</h2>
<h2>Counter 3</h2>
<a href="logout.php">Logout</a>
<table border="1">
<tr><th>ID</th><th>Username</th><th>Image</th><th>Action</th></tr>
<tr>
<td>1</td>
<td>hana</td>
<td><img width="70" height="70" src="uploads/hana.png" alt=""></td>
<td>
<a href="update.php?id=1">Edit</a>
<form action="delete.php" method="post" style="display:inline"
onsubmit="return confirm('Delete this user?');">
<input type="hidden" name="id" value="1">
<button type="submit">Delete</button>
</form>
</td>
</tr>
<tr>
<td>2</td>
<td>rezan</td>
<td><img width="70" height="70" src="uploads/rezan.jpg" alt=""></td>
<td>
<a href="update.php?id=2">Edit</a>
<form action="delete.php" method="post" style="display:inline"
onsubmit="return confirm('Delete this user?');">
<input type="hidden" name="id" value="2">
<button type="submit">Delete</button>
</form>
</td>
</tr>
</table>
</body>
</html>A visitor without a session gets no output at all, only the redirect.
سەردانیکەرێک بێ سێشن هیچ دەرخستەیەک وەرناگرێت، تەنها ئاڕاستەکردنەوەکە.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
Start the session before any HTML.
سێشن پێش هەر HTMLێک دەست پێبکە.
- Lineدێڕی 4-5
Not logged in: go to the login page and stop.
نەچووەتە ژوورەوە: بڕۆ بۆ لاپەڕەی چوونەژوورەوە و بوەستە.
- Lineدێڕی 9
The visit counter, written in one line with
??.ژمێرەری سەردان، بە یەک دێڕ بە
??نووسراوە.- Lineدێڕی 10
Select only what the page shows: no passwords.
تەنها ئەوە هەڵبژێرە کە لاپەڕەکە پیشانی دەدات: بێ تێپەڕەوشە.
<?php
session_start();
if (!isset($_SESSION['userId'])) {
header('Location: login.php');
exit;
}
require "config.php";
$id = filter_var($_GET['id'] ?? null, FILTER_VALIDATE_INT);
if ($id === false || $id === null) {
header('Location: View.php');
exit;
}
if (isset($_POST['Update'])) {
$username = trim($_POST['user'] ?? '');
$newPass = $_POST['pass'] ?? '';
if ($username !== '') {
$stmt = $conn->prepare("UPDATE users SET username = ? WHERE id = ?");
$stmt->bind_param("si", $username, $id);
$stmt->execute();
if ($newPass !== '') { // only when a NEW password was typed
$hash = password_hash($newPass, PASSWORD_DEFAULT);
$stmt = $conn->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->bind_param("si", $hash, $id);
$stmt->execute();
}
header('Location: View.php');
exit;
}
}
$stmt = $conn->prepare("SELECT username FROM users WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
$user = $stmt->get_result()->fetch_assoc();
if ($user === null) {
header('Location: View.php');
exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<body>
<form method="post">
username : <input type="text" name="user" value="<?= e($user['username']) ?>"><br><br>
new password (leave empty to keep it) : <input type="password" name="pass"><br><br>
<input type="submit" name="Update" value="Update">
</form>
</body>
</html>POST user="hana " with an empty password box -> username saved as [hana], password hash unchanged
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 9-13
Validate the id from the URL before anything else.
پێش هەموو شتێک idـی URLـەکە پشتڕاست بکەرەوە.
- Lineدێڕی 16
trim()removes accidental spaces.trim()بۆشایی بە هەڵە لادەبات.- Lineدێڕی 22-27
Change the password only when a new one is typed, and use
password_hash().تەنها کاتێک تێپەڕەوشە بگۆڕە کە نوێیەک نووسرابێت و
password_hash()بەکاربهێنە.
<?php
session_start();
if (!isset($_SESSION['userId'])) {
header('Location: login.php');
exit;
}
require "config.php";
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = filter_var($_POST['id'] ?? null, FILTER_VALIDATE_INT);
if ($id !== false && $id !== null) {
// find the user's picture first, so we can remove the file too
$stmt = $conn->prepare("SELECT filename FROM users WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
$user = $stmt->get_result()->fetch_assoc();
if ($user !== null) {
$stmt = $conn->prepare("DELETE FROM users WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
$file = "uploads/" . basename($user['filename']);
if (is_file($file)) {
unlink($file);
}
}
}
}
header('Location: View.php');
exit;GET delete.php?id=2 -> nothing deleted POST id=1' OR '1'='1 -> nothing deleted POST id=2 -> user 2 and uploads/rezan.jpg deleted
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 13-16
Read the picture's file name first.
سەرەتا ناوی فایلی وێنەکە بخوێنەوە.
- Lineدێڕی 23-26
basename()keeps the path insideuploads/; then delete the file.basename()ڕێڕەوەکە لەناوuploads/دا دەهێڵێتەوە؛ پاشان فایلەکە بسڕەوە.
A complete secure login (with password_verify()) for this project is built in A login system. Note that here any logged-in user may still edit every user; a real admin area would also check the user's role.
چوونەژوورەوەیەکی تەواو و سەلامەت (بە password_verify()) بۆ ئەم پڕۆژەیە لە سیستەمی چوونەژوورەوە دروست دەکرێت. ئاگاداربە کە لێرەدا هەر بەکارهێنەرێکی چووەژوورەوە هێشتا دەتوانێت هەموو بەکارهێنەرێک دەستکاری بکات؛ بەشێکی ڕاستەقینەی بەڕێوەبەر ڕۆڵی بەکارهێنەریش دەپشکنێت.
Find the bugs: students' CRUD projectsباگەکان بدۆزەرەوە: پڕۆژەکانی CRUDـی قوتابیان
C. The diagram's update and deleteC. update و deleteـی ناو وێنەکە
These are the update.php and delete.php boxes from the student's page-map diagram. List what is wrong with them.
ئەمانە سندوقەکانی update.php و delete.phpـن لە وێنەی نەخشەی لاپەڕەکانی قوتابییەکەدا. بنووسە چییان تێدا هەڵەیە.
<html>
<body>
<?php
include "Config.php";
if (isset($_GET['id'])) {
$user_id = $_GET['id'];
$sql = "select * from student where id='$user_id'";
$result = mysqli_query($db_connect, $sql);
while ($row = mysqli_fetch_assoc($result)) {
$username = $row['username']; //Used in the HTML (usefull for sticky form)
$email = $row['email'];
$password = $row['password'];
$gender = $row['gender'];
$id = $row['id'];
}
}
if (isset($_POST['update'])) {
$usernamee = $_POST['username'];
$user_id = $_POST['id'];
$email = $_POST['email'];
$gender = $_POST['gender'];
$password = $_POST['password'];
$sql = "update student set username='$usernamee',email='$email', password='$password',gender='$gender' where id='$user_id'";
$result = mysqli_query($db_connect, $sql);
if ($result) {
echo "Record updated successfully." . header('Location: view.php');
} else {
echo "Error:" . mysqli_error($db_connect);
}
}
?>
<h2>Update Form</h2>
<form method="post">
<fieldset>
<legend>Personal information:</legend>
First name:<br>
<input type="text" name="username" value="<?php echo $username; ?>">
<input type="hidden" name="id" value="<?php echo $id; ?>"><br />
Last name:<br />
<input type="text" name="email" value="<?php echo $email; ?>"><br />
Password:<br />
<input type="password" name="password" value="<?php echo $password; ?>"><br />
Gender:<br />
<input type="radio" name="gender" value="Male" <?php if ($gender == 'Male') echo "checked"; ?>>Male
<input type="radio" name="gender" value="Female" <?php if ($gender == 'Female') echo "checked"; ?>>Female
<br /><br />
<input type="submit" value="Update" name="update">
</fieldset>
</form>
</body>
</html><?php
include("Config.php");
$user_id = $_GET['id'];
$sql = "delete from student where id = $user_id";
$result = mysqli_query($db_connect, $sql);
if ($result) {
echo "sucess" . header('Location: view.php');
} else {
die(mysqli_error($db_connect));
}
?>Show answerوەڵامەکە پیشان بدە
- SQL injection in all three queries. In delete.php the id is not even in quotes, so
1 OR 1=1deletes every student. header()after HTML output in update.php:<html><body>is printed first, so the redirect fails with "headers already sent" (unless output buffering hides it).echo "Record updated successfully." . header(...)is also strange:header()returns nothing, and the message would never be seen after a redirect anyway. Redirect first, thenexit.- Password in plain text, stored and printed into the password box.
- Wrong labels: the field labelled "Last name" is really the email field;
$usernameeis a typo (it works only because it is used consistently). - Undefined variables when the page is opened without
?id=, and no escaping of the printed values (XSS). - delete.php: no
isset()check, deletion by GET, "sucess" is misspelt, anddie(mysqli_error(...))shows database details.
- لە هەر سێ پرسەکەدا. لە delete.phpـدا id تەنانەت لەناو کۆتەیشنیشدا نییە، بۆیە
1 OR 1=1هەموو قوتابییەک دەسڕێتەوە. header()دوای دەرخستەی HTML لە update.php: سەرەتا<html><body>چاپ دەکرێت، بۆیە ئاڕاستەکردنەوەکە بە «headers already sent» شکست دەهێنێت (مەگەر output buffering بیشارێتەوە).echo "Record updated successfully." . header(...)ـیش سەیرە:header()هیچ ناگەڕێنێتەوە و پەیامەکە دوای ئاڕاستەکردنەوە بە هەر حاڵ نابینرێت. سەرەتا ئاڕاستە بکەرەوە، پاشانexit.- تێپەڕەوشە وەک ، هەڵدەگیرێت و لەناو خانەی تێپەڕەوشەدا چاپ دەکرێت.
- ناونیشانی هەڵە: ئەو خانەیەی ناونیشانی «Last name»ـی هەیە لە ڕاستیدا خانەی ئیمەیڵە؛
$usernameeهەڵەی نووسینە (تەنها کار دەکات چونکە هەمیشە بە هەمان شێوە بەکارهاتووە). - گۆڕاوی پێناسەنەکراو کاتێک لاپەڕەکە بەبێ
?id=دەکرێتەوە و هیچ بێزیانکردنێک بۆ نرخە چاپکراوەکان نییە (XSS). - delete.php: پشکنینی
isset()نییە، سڕینەوە بە GET، «sucess» هەڵە نووسراوە وdie(mysqli_error(...))وردەکاری بنکەدراوە پیشان دەدات.
The secure update.php and delete.php of this lecture fix all of these.
update.php و delete.phpـی سەلامەتی ئەم وانەیە هەموو ئەمانە چاک دەکەن.
Lab tasksئەرکەکانی تاقیگە
These are the tasks of Lecture 8 and Lab 8. Together they build the whole application. The answers point to the secure code in this lecture.
ئەمانە ئەرکەکانی وانەی ٨ و تاقیگەی ٨ن. پێکەوە هەموو بەرنامەکە دروست دەکەن. وەڵامەکان ئاماژە بە کۆدە سەلامەتەکەی ئەم وانەیە دەکەن.
Establish a database connection with your PHP code. The code works as a link between the web page and the database. Name the file config.php.
بە کۆدی PHPـەکەت دروست بکە. کۆدەکە وەک پردێک لە نێوان لاپەڕەکە و بنکەدراوەکەدا کار دەکات. ناوی فایلەکە بنێ config.php.
Show answerوەڵامەکە پیشان بدە
See the improved config.php: mysqli_report(), new mysqli() inside try, set_charset("utf8mb4"), a short message on failure, and the e() helper.
config.phpـی باشترکراو ببینە: mysqli_report()، new mysqli() لەناو try، set_charset("utf8mb4")، پەیامێکی کورت لە کاتی شکستدا و کردارە یاریدەدەرەکەی e().
Create: write the page that inserts a new user from the Signup form (first name, last name, email, password, gender). Name it create.php.
Create: ئەو لاپەڕەیە بنووسە کە بەکارهێنەرێکی نوێ لە فۆرمی خۆتۆمارکردنەوە زیاد دەکات (ناوی یەکەم، ناوی کۆتایی، ئیمەیڵ، تێپەڕەوشە، ڕەگەز). ناوی بنێ create.php.
Show answerوەڵامەکە پیشان بدە
See the secure create.php: server-side validation, a duplicate-email check, password_hash(), a prepared INSERT, and a redirect to view.php.
create.phpـی سەلامەت ببینە: ، پشکنینی ئیمەیڵی دووبارە، password_hash()، INSERTێکی ئامادەکراو و ئاڕاستەکردنەوە بۆ view.php.
Read: create a page that displays the records from the table users. Name it view.php.
Read: لاپەڕەیەک دروست بکە کە تۆمارەکانی خشتەی users پیشان بدات. ناوی بنێ view.php.
Show answerوەڵامەکە پیشان بدە
See the secure view.php: a SELECT of the needed columns, every value printed with e(), an Edit link and a Delete POST form for every row.
view.phpـی سەلامەت ببینە: SELECTێک بۆ ستوونە پێویستەکان، هەموو نرخێک بە e() چاپ دەکرێت، بەستەری Edit و فۆرمی POSTـی Delete بۆ هەموو ڕیزێک.
Update: create another page to update the details of a user in the database. Name it update.php.
Update: لاپەڕەیەکی تر دروست بکە بۆ نوێکردنەوەی وردەکارییەکانی بەکارهێنەرێک لە بنکەدراوەکەدا. ناوی بنێ update.php.
Show answerوەڵامەکە پیشان بدە
See the secure update.php: GET with ?id= loads the user with a prepared SELECT; the POST validates, runs a prepared UPDATE, changes the password only when a new one is typed, and redirects.
update.phpـی سەلامەت ببینە: GET بە ?id= بەکارهێنەرەکە بە SELECTێکی ئامادەکراو دەهێنێت؛ POSTـەکە پشتڕاست دەکاتەوە، UPDATEێکی ئامادەکراو جێبەجێ دەکات، تەنها کاتێک تێپەڕەوشە دەگۆڕێت کە نوێیەک نووسرابێت و ئاڕاستە دەکاتەوە.
Delete (the in-class task): create a page that lets the user choose a record and delete it from the database. Name it delete.php.
Delete (ئەرکی ناو پۆل): لاپەڕەیەک دروست بکە کە ڕێگە بە بەکارهێنەر بدات تۆمارێک هەڵبژێرێت و لە بنکەدراوەکەدا بیسڕێتەوە. ناوی بنێ delete.php.
Hintئاماژە
The user chooses the record with the Delete button in view.php.
بەکارهێنەر تۆمارەکە بە دوگمەی Delete لە view.phpـدا هەڵدەبژێرێت.
Show answerوەڵامەکە پیشان بدە
<?php
require "config.php";
// Deleting changes data, so accept it only from a POST form - never from a plain link
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user_id = filter_var($_POST['id'] ?? null, FILTER_VALIDATE_INT);
if ($user_id !== false && $user_id !== null) {
$stmt = $conn->prepare("DELETE FROM users WHERE id = ?");
$stmt->bind_param("i", $user_id);
$stmt->execute();
}
}
header("Location: view.php");
exit;The Delete button in view.php is a small POST form with a hidden id and a confirm() question. Lecture 8's own answer is shown, with its problems, in the Delete section.
دوگمەی Delete لە view.phpـدا فۆرمێکی بچووکی POSTـە بە idـێکی شاراوە و پرسیارێکی confirm(). وەڵامی خودی وانەی ٨، لەگەڵ کێشەکانی، لە بەشی Deleteدا پیشان دراوە.
Summary and reviewپوختە و پێداچوونەوە
| Pageلاپەڕە | Requestداواکاری | SQL | Key safety pointsخاڵە سەرەکییەکانی سەلامەتی |
|---|---|---|---|
config.php | included by allلە هەمووان هاوردە دەکرێت | — | try/catch, utf8mb4, own DB user, e()try/catch، utf8mb4، بەکارهێنەری تایبەتی بنکەدراوە، e() |
create.php | GET form, POST saveفۆرمی GET، پاشەکەوتی POST | INSERT | validate, password_hash, prepared, redirect + exitپشتڕاستکردنەوە، password_hash، ئامادەکراو، ئاڕاستەکردنەوە + exit |
view.php | GET | SELECT | named columns, escape every value, Delete as POSTستوونی ناودار، بێزیانکردنی هەموو نرخێک، Delete بە POST |
update.php | GET ?id=, POST saveGET ?id=، پاشەکەوتی POST | UPDATE | validate the hidden id, never pre-fill passwordsidـی شاراوە پشتڕاست بکەرەوە، هەرگیز تێپەڕەوشە پێشوەختە پڕ مەکەرەوە |
delete.php | POST onlyتەنها POST | DELETE | validate id, prepared, confirm, (CSRF token)پشتڕاستکردنەوەی id، ئامادەکراو، confirm، (نیشانەی CSRF) |
What does CRUD stand for, and which SQL command belongs to each letter?CRUD کورتکراوەی چییە و کام فەرمانی SQL سەر بە هەر پیتێکە؟
Create (INSERT), Read (SELECT), Update (UPDATE), Delete (DELETE): the four functions needed for a persistent storage application.
Create (INSERT)، Read (SELECT)، Update (UPDATE)، Delete (DELETE): ئەو چوار کارەی بۆ بەرنامەیەکی پێویستن.
What is a persistent storage application?بەرنامەی خەزنکردنی هەمیشەیی چییە؟
One that keeps data after the program or computer stops, unlike RAM or a cache. Examples: databases, file systems, object storage.
بەرنامەیەکە کە دراوە دوای وەستانی بەرنامەکە یان کۆمپیوتەرەکە دەپارێزێت، بە پێچەوانەی RAM یان cache. نموونە: بنکەدراوە، ، هەڵگرتنی ئۆبجێکت.
Does using CRUD make an application secure?ئایا بەکارهێنانی CRUD بەرنامەیەک سەلامەت دەکات؟
No. CRUD only names the operations. Security comes from login checks, authorization checks (for example WHERE owner_id = ?), prepared statements, validation and escaping.
نەخێر. CRUD تەنها ناوی کارەکانە. ئاسایش لە پشکنینی چوونەژوورەوە، پشکنینی ڕێگەپێدان (بۆ نموونە WHERE owner_id = ?)، ، پشتڕاستکردنەوە و بێزیانکردنەوە دێت.
What do num_rows and fetch_assoc() do in view.php?num_rows و fetch_assoc() لە view.phpـدا چی دەکەن؟
num_rows is the number of rows in the result; fetch_assoc() returns the next row as an associative array, or null when there are no more.
num_rows ژمارەی ڕیزەکانی ئەنجامەکەیە؛ fetch_assoc() ڕیزی دواتر وەک دەگەڕێنێتەوە، یان null کاتێک هیچی تر نەماوە.
Why is exit; written after header("Location: view.php")?بۆچی exit; دوای header("Location: view.php") دەنووسرێت؟
header() only adds a header; the script keeps running. Without exit, a protected page can still send its content to someone who is not logged in.
header() تەنها سەردێڕێک زیاد دەکات؛ دەسنووسەکە بەردەوام دەبێت. بەبێ exit، لاپەڕەیەکی پارێزراو هێشتا دەتوانێت ناوەڕۆکەکەی بنێرێت بۆ کەسێک کە نەچووەتە ژوورەوە.
What is Post/Redirect/Get?Post/Redirect/Get چییە؟
After a POST that changes data, redirect to a GET page. Refreshing then repeats only the harmless GET and does not insert the data twice.
دوای POSTێک کە دراوە دەگۆڕێت، ئاڕاستە بکەرەوە بۆ لاپەڕەیەکی GET. ئەوکات نوێکردنەوەی لاپەڕە تەنها GETـە بێزیانەکە دووبارە دەکاتەوە و دراوەکە دوو جار زیاد ناکات.
Why should the password box of an edit form be empty?بۆچی خانەی تێپەڕەوشەی فۆرمی دەستکاریکردن دەبێت بەتاڵ بێت؟
Pre-filling it shows the stored value in the page source, and saving it again can hash the hash so the password stops working. Change the password only when a new one is typed.
پڕکردنەوەی پێشوەختە نرخە هەڵگیراوەکە لە سەرچاوەی لاپەڕەکەدا پیشان دەدات و پاشەکەوتکردنەوەی دەتوانێت هاشەکە هاش بکاتەوە بە شێوەیەک کە تێپەڕەوشەکە ئیتر کار نەکات. تەنها کاتێک تێپەڕەوشە بگۆڕە کە نوێیەک نووسرابێت.
Videoڤیدیۆ
Watch a videoسەیری ڤیدیۆیەک بکە
A video that viewers liked a lot and praised in their comments, chosen for this lesson.ڤیدیۆیەک کە بینەران زۆریان پێ خۆش بووە و لە کۆمێنتەکانیاندا ستایشیان کردووە، بۆ ئەم وانەیە هەڵبژێردراوە.
Watch a full CRUD app being built with PHP and PDO: list products, add them, delete them with a POST form, and edit them.
سەیر بکە چۆن تەواوی بە و دروست دەکرێت: لیستی بەرهەمەکان، زیادکردنیان، سڕینەوەیان بە POST و دەستکاریکردنیان.
Watch 2:33:53–3:58:22: a products CRUD with PDO, from listing to update.
لە 2:33:53 تا 3:58:22 سەیر بکە: بەرهەمەکان بە ، لە لیستکردنیانەوە تا دەستکاریکردنیان.
Lesson complete!وانەکە تەواو بوو!
Well done. Review the key terms, then go on to the next lesson.دەستخۆش. سەیرێکی زاراوە سەرەکییەکان بکەرەوە، پاشان بڕۆ بۆ وانەی داهاتوو.
Key termsزاراوە سەرەکییەکان
SQLite Basicsبنەماکانی SQLite
Related lecturesوانە پەیوەندیدارەکان
References and further readingسەرچاوەکان و خوێندنەوەی زیاتر
This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.