Chapterبەش 9 · Lectureوانە 9.6
Inserting and selecting data with PHPزیادکردن و هەڵبژاردنی زانیاری بە PHP
Save what a visitor types with prepared INSERT statements and hashed passwords (Lab 7's registration page, corrected), read rows back with SELECT and fetch, count them, and split long lists into pages with LIMIT and OFFSET.
ئەوەی سەردانکەر دەینووسێت بە فەرمانی INSERTـی ئامادەکراو و هاشکراو هەڵبگرە (لاپەڕەی خۆتۆمارکردنی تاقیگەی 7، ڕاستکراوە)، بە SELECT و fetch بخوێنەوە، بیانژمێرە و لیستە درێژەکان بە LIMIT و OFFSET بکە بە چەند لاپەڕەیەک.
What you will learnچی فێر دەبیت
- Insert a record with fixed values and check the result with try/catch.
- Insert visitor input safely with mysqli and PDO prepared statements, and store passwords with password_hash().
- Build and fix the Lab 7 registration page.
- Read rows with SELECT and a while loop, and explain fetch, the three fetch modes and num_rows.
- Use LIMIT and OFFSET for pagination and compute the offset of any page.
- بە نرخی جێگیر زیاد بکەیت و ئەنجامەکە بە try/catch بپشکنیت.
- نێوخستەی سەردانکەر بە سەلامەتی بە mysqli و PDO زیاد بکەیت و بە password_hash() هەڵبگریت.
- لاپەڕەی خۆتۆمارکردنی تاقیگەی 7 دروست بکەیت و چاکی بکەیت.
- بە SELECT و بخوێنیتەوە و fetch، سێ شێوازی fetch و num_rows ڕوون بکەیتەوە.
- LIMIT و OFFSET بۆ بەکار بهێنیت و OFFSETـی هەر لاپەڕەیەک بژمێریت.
Saving and reading data from PHPهەڵگرتن و خوێندنەوەی زانیاری لە PHPـەوە
You can now connect to MySQL. This lecture does the two jobs that almost every database page needs: saving what a visitor types with INSERT, and reading rows back with SELECT to show them on a page. At the end you will show long lists one page at a time with LIMIT and OFFSET.
ئێستا دەتوانیت پەیوەندی بە MySQLـەوە بکەیت. ئەم وانەیە ئەو دوو کارە دەکات کە نزیکەی هەموو لاپەڕەیەکی پێویستیان پێیەتی: هەڵگرتنی ئەوەی سەردانکەر دەینووسێت بە INSERT و خوێندنەوەی بە SELECT بۆ پیشاندانیان لە لاپەڕەیەکدا. لە کۆتاییدا لیستە درێژەکان لاپەڕە بە لاپەڕە پیشان دەدەیت بە LIMIT و OFFSET.
Every page starts with the connection file from Connecting PHP to MySQL. The examples use dbcon.php (database mydb, table stage_4 from phpMyAdmin) or Lab 7's connection.php (database php_mysql, table users).
هەموو لاپەڕەیەک بە فایلی پەیوەندییەکەی پەیوەندیکردنی PHP بە MySQL دەست پێ دەکات. نموونەکان dbcon.php ( mydb، stage_4 لە phpMyAdmin) یان connection.phpـی تاقیگەی 7 ( php_mysql، users) بەکار دەهێنن.
<?php
// dbcon.php: the connection file used on this page's examples
try {
$con = mysqli_connect("localhost", "root", "", "mydb");
mysqli_set_charset($con, "utf8mb4");
} catch (mysqli_sql_exception $e) {
error_log($e->getMessage());
exit("Sorry, the database is not available right now.");
}(no output when the connection works)
Checked with php -l (no syntax errors). It cannot run in our sandbox because there is no MySQL server there.
بە php -l پشکنراوە (هیچ نییە). لە ژینگەی تاقیکردنەوەکەماندا ناتوانێت کار بکات چونکە MySQL لەوێ نییە.
Inserting a recordزیادکردنی تۆمارێک
The simplest insert has fixed values that you write yourself — no visitor input — so a normal query is fine. The connection object has a method query() that sends SQL to the database:
سادەترین زیادکردن نرخی جێگیری هەیە کە خۆت دەیاننووسیت — بێ نێوخستەی سەردانکەر — بۆیە ئاسایی باشە. پەیوەندییەکە query()ـی هەیە کە SQL دەنێرێت بۆ :
<?php
include "dbcon.php"; // the connection file: it creates $con
$sql = "INSERT INTO `stage_4` (`name`, `age`, `salary`)
VALUES ('Dilan', 24, 350)";
try {
$con->query($sql); // the same as: mysqli_query($con, $sql);
echo "New record created successfully. Its id is " . $con->insert_id;
} catch (mysqli_sql_exception $e) {
error_log($e->getMessage());
echo "Error: the record was not saved.";
}New record created successfully. Its id is 4
Real output on the stage_4 table that already holds the three rows from the phpMyAdmin lecture (Aram, Shilan, Karwan). This exact code was run against SQLite through a small mysqli test layer (our sandbox has no MySQL server).
ڕاستەقینە لەسەر stage_4 کە پێشتر سێ وانەی phpMyAdminی تێدایە (Aram، Shilan، Karwan). هەمان ئەم کۆدە لەسەر SQLite لە ڕێگەی چینێکی بچووکی تاقیکردنەوەی mysqliـەوە جێبەجێ کرا (ژینگەی تاقیکردنەوەکەمان MySQLـی تێدا نییە).
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
The connection file creates
$con.فایلی پەیوەندییەکە
$conدروست دەکات.- Lineدێڕی 4-5
Backticks mark
stage_4,name, ... as names, not keywords.idis left out becauseAUTO_INCREMENTfills it in.باکتیکەکان
stage_4،name، ... وەک ناو دیاری دەکەن، نەک .idنەنووسراوە چونکەAUTO_INCREMENTپڕی دەکاتەوە.- Lineدێڕی 8
query()sends the SQL to the database. For anINSERTit returnstruewhen it works.query()SQLـەکە دەنێرێت بۆ . بۆINSERTکاتێک سەرکەوتوو بێتtrueدەگەڕێنێتەوە.- Lineدێڕی 9
insert_idis a property: theidthatAUTO_INCREMENTgave the new row.insert_id: ئەوidـەیAUTO_INCREMENTداوێتی بە نوێیەکە.- Lineدێڕی 10-13
If MySQL refuses the row, PHP 8.1+ throws
mysqli_sql_exceptionand we land here.ئەگەر MySQL ڕەت کردەوە، PHP 8.1+
mysqli_sql_exceptionفڕێ دەدات و دەگەینە ئێرە.
- The backtick (
`, on the slide): it tells MySQL that the word inside is a table or column name, not a keyword. query(): sends your SQL to the database and returnsTRUEwhen anINSERTworked.- Always check the result: know whether the row really went in, instead of guessing. In PHP 8.1+ "checking" means catching the exception.
- باکتیک (لە سلایدەکەدا): بە MySQL دەڵێت ئەو وشەیەی لە ناویدایە ناوی یان ، نەک .
query(): SQLـەکەت دەنێرێت بۆ و کاتێکINSERTێک سەرکەوتوو بووTRUEدەگەڕێنێتەوە.- هەمیشە ئەنجامەکە بپشکنە: بزانە ئایا بەڕاستی چووە ژوورەوە، لە جیاتی گومانکردن. لە PHP 8.1+دا «پشکنین» واتە گرتنی .
Inserting a recordزیادکردنی تۆمارێک
The slide's version had a hidden bugوەشانەکەی سلایدەکە هەڵەیەکی شاراوەی تێدا بوو
Inserting what a visitor types: prepared statementsزیادکردنی ئەوەی سەردانکەر دەینووسێت: ڕستە ئامادەکراوەکان
As soon as a value comes from a visitor, you cannot glue it into the SQL text. Look what happens when the course's way (VALUES ('$name', '$pass')) meets two names:
هەر کە نرخێک لە سەردانکەرەوە هات، ناتوانیت بە دەقی SQLـەکەوە بیلکێنیت. سەیر بکە چی ڕوو دەدات کاتێک ڕێگاکەی وانەکە (VALUES ('$name', '$pass')) دوو ناو وەردەگرێت:
<?php
// Why we never paste form values into SQL (run with SQLite)
$pdo = new PDO("sqlite::memory:");
$pdo->exec("CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, password TEXT)");
$pdo->exec("INSERT INTO log (name, password) VALUES ('Karwan', 'k-secret-1')");
function unsafe_insert(PDO $pdo, string $name, string $pass): void {
$sql = "INSERT INTO log (name, password) VALUES ('$name', '$pass')";
echo "SQL: $sql\n";
try { $pdo->exec($sql); echo "Result: inserted\n\n"; }
catch (PDOException $e) { echo "Result: ERROR, the query is broken\n\n"; }
}
unsafe_insert($pdo, "Hana O'Brien", "1234"); // an honest name with '
unsafe_insert($pdo, "x', (SELECT password FROM log WHERE name = 'Karwan')) -- ", "anything");
// The safe way: a prepared statement
$stmt = $pdo->prepare("INSERT INTO log (name, password) VALUES (?, ?)");
$stmt->execute(["Hana O'Brien", "1234"]);
foreach ($pdo->query("SELECT id, name, password FROM log") as $r) {
echo $r["id"] . " | " . $r["name"] . " | " . $r["password"] . "\n";
}SQL: INSERT INTO log (name, password) VALUES ('Hana O'Brien', '1234')
Result: ERROR, the query is broken
SQL: INSERT INTO log (name, password) VALUES ('x', (SELECT password FROM log WHERE name = 'Karwan')) -- ', 'anything')
Result: inserted
1 | Karwan | k-secret-1
2 | x | k-secret-1
3 | Hana O'Brien | 1234
Real output (SQLite). Row 2 is the attacker's row holding Karwan's password; row 3 is O'Brien saved correctly by the prepared statement. (Passwords are stored as plain text here only to make the danger visible.)
ڕاستەقینە (SQLite). 2 هێرشبەرەکەیە کە تێپەڕەوشەکەی Karwanی تێدایە؛ 3 O'Brienـە کە بە دروستی هەڵیگرتووە. ( لێرە تەنها بۆ دیارکردنی مەترسییەکە وەک دەقی ئاسایی هەڵگیراون.)
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 8
The unsafe way: the values become part of the SQL text.
ڕێگا نا-سەلامەتەکە: نرخەکان دەبنە بەشێک لە دەقی SQLـەکە.
- Lineدێڕی 14
An honest surname with an apostrophe: the
'ends the text early and the SQL breaks.پاشناوێکی ئاسایی کە ئەپۆسترۆفی تێدایە:
'دەقەکە زوو تەواو دەکات و SQLـەکە تێک دەچێت.- Lineدێڕی 15
A crafted "name" changes the meaning of the SQL: it copies Karwan's stored password into a new row. This is .
«ناو»ێکی دەستکرد واتای SQLـەکە دەگۆڕێت: هەڵگیراوەکەی Karwan کۆپی دەکات بۆ نوێ. ئەمە .
- Lineدێڕی 18-19
The safe way:
?marks where the values go; the values are sent separately.ڕێگا سەلامەتەکە:
?شوێنی نرخەکان دیاری دەکات؛ نرخەکان بە جیا دەنێردرێن.
An SQL statement sent to the database with placeholders (? or :name) instead of values. The values are sent separately, so is never run as SQL. This is the main defence against .
فەرمانێکی SQLـە کە بە شوێنگرەوە (? یان :name) لە جیاتی نرخەکان دەنێردرێت بۆ . نرخەکان بە جیا دەنێردرێن، بۆیە هەرگیز وەک SQL جێبەجێ ناکرێت. ئەمە سەرەکیترین ڕێگای بەرگرییە دژی .
A prepared statement is like a printed bank deposit slip. The bank printed the text ("Pay into account number ____ the amount ____"); you only write in the empty boxes. Whatever you write in a box — even the words "and close the account" — stays an answer in that box. It can never change the printed instructions.
وەک پسوولەیەکی چاپکراوی دانانی پارە وایە لە بانکدا. بانکەکە دەقەکەی چاپ کردووە («بخەرە سەر ژمارەی هەژماری ____ بڕی ____»)؛ تۆ تەنها خانە بەتاڵەکان پڕ دەکەیتەوە. هەرچی لە خانەیەکدا بنووسیت — تەنانەت وشەی «و هەژمارەکە دابخە» — هەر وەڵامێک دەمێنێتەوە لەو خانەیەدا. هەرگیز ناتوانێت ڕێنماییە چاپکراوەکان بگۆڕێت.
Where the analogy stops workingسنووری ئەم نموونەیە
The empty boxes can only hold values. A table name or a column name cannot be sent as a ?; choose those in your code from a fixed list.
خانە بەتاڵەکان تەنها دەتوانن نرخ هەڵبگرن. ناوی یان ناوی ناتوانرێت وەک ? بنێردرێت؛ ئەوانە لە کۆدەکەتدا لە لیستێکی جێگیرەوە هەڵبژێرە.
Write an INSERT with ? placeholders, see why pasting form values into SQL is dangerous, then bind the values and save the row.
بە ? دەنووسیت، دەبینیت بۆچی خستنەناوی ڕاستەوخۆی بۆ ناو مەترسیدارە، پاشان دەبەستیتەوە و پاشەکەوت دەکەیت.
Watch 19:50–24:06: INSERT with placeholders, the injection risk, bind and execute
لە 19:50 تا 24:06 سەیر بکە: بە ، مەترسیی ، پاشان بەستنەوەی و جێبەجێکردن.
Inserting what a visitor types: prepared statementsزیادکردنی ئەوەی سەردانکەر دەینووسێت: ڕستە ئامادەکراوەکان
With mysqli: prepare, bind, executeبە mysqli: ئامادەکردن، بەستنەوە، جێبەجێکردن
<?php
include "dbcon.php";
// Imagine these three values came from a form
$name = "Hana O'Brien";
$age = 23;
$salary = 450;
// 1. Prepare: the SQL has ? where the values will go
$stmt = mysqli_prepare($con, "INSERT INTO stage_4 (name, age, salary) VALUES (?, ?, ?)");
// 2. Bind: "sii" = string, integer, integer (one letter per ?)
mysqli_stmt_bind_param($stmt, "sii", $name, $age, $salary);
// 3. Execute: the values travel separately from the SQL
mysqli_stmt_execute($stmt);
echo "Saved " . mysqli_stmt_affected_rows($stmt) . " row. New id: " . mysqli_insert_id($con);
mysqli_stmt_close($stmt);Saved 1 row. New id: 5
Real output after the insert above (the table now holds four rows). This exact code was run against SQLite through a small mysqli test layer; the PDO version below runs as it is.
ڕاستەقینە دوای زیادکردنەکەی سەرەوە ( ئێستا چوار تێدایە). هەمان ئەم کۆدە لەسەر SQLite لە ڕێگەی چینێکی بچووکی تاقیکردنەوەی mysqliـەوە جێبەجێ کرا؛ وەشانی PDOـی خوارەوە وەک خۆی کار دەکات.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 10
Prepare: send the SQL with a
?for each value. No quotes around?.ئامادەکردن: SQLـەکە بنێرە لەگەڵ
?بۆ هەر نرخێک. هیچ کۆتەیشنێک بە دەوری?دا دامەنێ.- Lineدێڕی 13
Bind: connect PHP variables to the markers. The type string has one letter per
?:sstring,iinteger,ddecimal (float),bblob.بەستنەوە: PHP بە نیشانەکانەوە ببەستە. زنجیرەی جۆرەکان بۆ هەر
?ێک یەک پیتی هەیە:s،i،d،bblob.- Lineدێڕی 16
Execute: the values travel separately from the SQL. In PHP 8.1+ a failure throws.
جێبەجێکردن: نرخەکان جیا لە SQLـەکە دەڕۆن. لە PHP 8.1+دا شکست فڕێ دەدات.
- Lineدێڕی 18
mysqli_stmt_affected_rows()= rows added;mysqli_insert_id()= the newid.mysqli_stmt_affected_rows()= ئەو زیاد کراون؛mysqli_insert_id()=idـە نوێیەکە.
| Letterپیت | Type of the valueجۆری نرخەکە | Exampleنموونە |
|---|---|---|
s | (also dates and emails) (هەروەها بەروار و ) | "Hana O'Brien" |
i | 23 | |
d | (decimal) | 12.5 |
b | binary data (), sent in pieces دووانەیی ()، پارچە پارچە دەنێردرێت | an image fileفایلی وێنە |
Inserting what a visitor types: prepared statementsزیادکردنی ئەوەی سەردانکەر دەینووسێت: ڕستە ئامادەکراوەکان
With PDO: prepare and executeبە PDO: ئامادەکردن و جێبەجێکردن
PDO is shorter: give the values to execute() as an array. You can use ? or named markers such as :name. This one really runs (with SQLite):
PDO کورتترە: نرخەکان وەک بدە بە execute(). دەتوانیت ? یان نیشانەی ناودار وەک :name بەکار بهێنیت. ئەمەیان بەڕاستی کار دەکات (لەگەڵ SQLite):
<?php
// The same insert with PDO (shown here with SQLite so it can run anywhere)
$pdo = new PDO("sqlite::memory:");
$pdo->exec("CREATE TABLE stage_4 (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(100) NOT NULL, age INT NOT NULL, salary INT NOT NULL)");
$name = "Hana O'Brien";
$age = 23;
$salary = 450;
$stmt = $pdo->prepare("INSERT INTO stage_4 (name, age, salary) VALUES (?, ?, ?)");
$stmt->execute([$name, $age, $salary]);
echo "Saved " . $stmt->rowCount() . " row. New id: " . $pdo->lastInsertId() . "\n";
// Named markers such as :name work too
$stmt = $pdo->prepare("INSERT INTO stage_4 (name, age, salary) VALUES (:name, :age, :salary)");
$stmt->execute(["name" => "Soran", "age" => 30, "salary" => 600]);
foreach ($pdo->query("SELECT * FROM stage_4") as $row) {
echo $row["id"] . " | " . $row["name"] . " | " . $row["age"] . " | " . $row["salary"] . "\n";
}Saved 1 row. New id: 1 1 | Hana O'Brien | 23 | 450 2 | Soran | 30 | 600
Real output (SQLite). The apostrophe in O'Brien is stored safely.
ڕاستەقینە (SQLite). ئەپۆسترۆفەکەی O'Brien بە سەلامەتی هەڵگیراوە.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 10-11
Prepare with
?, then execute with the values in the same order.بە
?ئامادە بکە، پاشان بە نرخەکان بە هەمان ڕیزبەندی جێبەجێی بکە.- Lineدێڕی 15-16
Named markers: the array keys match
:name,:age,:salary.نیشانەی ناودار: کلیلەکانی لەگەڵ
:name،:age،:salaryدەگونجێن.
A mark, ? or a name such as :email, written in a where a value will go. The value is sent separately, so it can never change the SQL. A placeholder can stand only for a value, never for a table or column name.
نیشانەیەکە، ? یان ناوێک وەک :email، کە لە لە شوێنی نرخێک دەنووسرێت. نرخەکە بە جیا دەنێردرێت، بۆیە هەرگیز ناتوانێت SQLـەکە بگۆڕێت. شوێنگر تەنها دەتوانێت جێگەی نرخێک بگرێتەوە، هەرگیز جێگەی ناوی یان نا.
More attacks and defences (passwords, XSS, least privilege) are in Database security.
هێرش و بەرگریی زیاتر (، XSS، کەمترین مۆڵەت) لە ئاسایشی بنکەدراوەدان.
A form that saves what is typedفۆرمێک کە ئەوەی دەنووسرێت هەڵدەگرێت
Now join a form and an insert. The flow has five steps:
ئێستا و زیادکردنێک پێکەوە ببەستە. ڕەوتەکە پێنج هەنگاوی هەیە:
- The visitor typesسەردانکەر دەنووسێت
a name and a password.
ناوێک و .
- The visitor presses goسەردانکەر go دادەگرێت
The form posts to itself.
بۆ خۆی دەنێرێت (POST).
- PHP reads $_POSTPHP $_POST دەخوێنێتەوە
Two values arrive.
دوو نرخ دەگەن.
- The SQL runsSQLـەکە جێبەجێ دەبێت
One new row in the table
log.یەک نوێ لە
logدا. - A message is shownپەیامێک پیشان دەدرێت
Successful, or not.
سەرکەوتوو، یان نا.
The table log has the columns id (INT, primary key, AUTO_INCREMENT), name (VARCHAR 50) and password (VARCHAR 255). Here is the page, written safely:
log id (INT، ، AUTO_INCREMENT)، name (VARCHAR 50) و password (VARCHAR 255)ی هەیە. ئەمە لاپەڕەکەیە، بە سەلامەتی نووسراوە:
<?php
include "dbcon.php";
$message = "";
if (isset($_POST["save"])) {
$name = trim($_POST["name"]);
$password = $_POST["password"];
if ($name === "" || $password === "") {
$message = "Please fill in both fields.";
} else {
$hash = password_hash($password, PASSWORD_DEFAULT); // store a hash, never the password
$stmt = mysqli_prepare($con, "INSERT INTO log (name, password) VALUES (?, ?)");
mysqli_stmt_bind_param($stmt, "ss", $name, $hash);
mysqli_stmt_execute($stmt); // throws an exception if it fails
$message = "Insert is successful.";
}
}
?>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
<input type="text" name="name" placeholder="your name"><br>
<input type="password" name="password" placeholder="your password"><br>
<input type="submit" name="save" value="go">
</form>
<p><?php echo htmlspecialchars($message); ?></p>[your name] [your password] [go] Insert is successful.
HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<form method="post" action="/save.php">
<input type="text" name="name" placeholder="your name"><br>
<input type="password" name="password" placeholder="your password"><br>
<input type="submit" name="save" value="go">
</form>
<p>Insert is successful.</p>Real output after submitting the name "Shilan" with the password "my-Pa55word". This exact code was run against SQLite through a small mysqli test layer. The row saved in the table: id 1, name Shilan, password $2y$12$... (60 characters; PHP before 8.4 writes $2y$10$...); password_verify("my-Pa55word", ...) returns true.
ڕاستەقینە دوای ناردنی ناوی «Shilan» بە «my-Pa55word». هەمان ئەم کۆدە لەسەر SQLite لە ڕێگەی چینێکی بچووکی تاقیکردنەوەی mysqliـەوە جێبەجێ کرا. ئەو لە هەڵگیرا: id 1، name Shilan، password $2y$12$... (60 پیت؛ PHPـی پێش 8.4 دەنووسێت $2y$10$...)؛ password_verify("my-Pa55word", ...) true دەگەڕێنێتەوە.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 5
Only run the insert when the form was submitted (the button's name is
save).تەنها کاتێک زیادکردنەکە جێبەجێ بکە کە نێردرابێت (ناوی دوگمەکە
saveـە).- Lineدێڕی 9-10
Do not save empty values.
نرخی بەتاڵ هەڵمەگرە.
- Lineدێڕی 12
Never store the real password:
password_hash()turns it into a 60-character hash. At login,password_verify()checks it.هەرگیز ڕاستەقینەکە هەڵمەگرە:
password_hash()دەیکات بە هاشێکی 60 پیتی. لە کاتی ،password_verify()دەیپشکنێت.- Lineدێڕی 14-16
A prepared statement with two string values (
"ss").بە دوو نرخی (
"ss").- Lineدێڕی 21
The form posts to this same page. Note the
echo;htmlspecialchars()protects against XSS through the URL.بۆ هەمان ئەم لاپەڕەیە دەنێرێت. سەرنجی
echoبدە؛htmlspecialchars()لە XSS لە ڕێگەی URLـەوە دەپارێزێت.- Lineدێڕی 23
A password field uses
type="password", so the password is not shown on screen.type="password"بەکار دەهێنێت، بۆیە لەسەر شاشە پیشان نادرێت.
A form that saves what is typedفۆرمێک کە ئەوەی دەنووسرێت هەڵدەگرێت
What was wrong in the slide's formچی هەڵە بوو لە فۆرمەکەی سلایدەکەدا
Lab 7: a registration page with PHP and MySQLتاقیگەی 7: لاپەڕەیەکی خۆتۆمارکردن بە PHP و MySQL
In Lab 7 you connect a PHP project to the php_mysql database and its users table (built in the phpMyAdmin lecture), and save new accounts from a registration form. The steps:
لە تاقیگەی 7دا پرۆژەیەکی PHP بە php_mysql و usersـەکەیەوە دەبەستیتەوە (کە لە وانەی phpMyAdmin دروست کرا) و هەژماری نوێ لە خۆتۆمارکردنەوە هەڵدەگریت. هەنگاوەکان:
- Create the database and the
userstable in phpMyAdmin. - Create the project
php_mysqlwith a registration form inindex.php. - Validate the form.
- Write
connection.php. - Read the values, protect the password and require the connection.
- Insert the user and report success or an error.
- Close the connection.
- و
usersلە phpMyAdmin دروست بکە. - پرۆژەی
php_mysqlبە خۆتۆمارکردن لەindex.phpدروست بکە. - پشتڕاست بکەرەوە.
connection.phpبنووسە.- نرخەکان بخوێنەوە، بپارێزە و پەیوەندییەکە require بکە.
- بەکارهێنەرەکە زیاد بکە و سەرکەوتن یان هەڵە ڕاپۆرت بکە.
- پەیوەندییەکە دابخە.
Lab 7: a registration page with PHP and MySQLتاقیگەی 7: لاپەڕەیەکی خۆتۆمارکردن بە PHP و MySQL
The registration formفۆرمی خۆتۆمارکردن
Create a PHP project called php_mysql (in NetBeans or simply a folder in htdocs) and, inside index.php, a form that lets visitors create an account. Lab 7 gives this form; it is shown here with three fixes:
پرۆژەیەکی PHP بە ناوی php_mysql دروست بکە (لە NetBeans یان تەنها فۆڵدەرێک لە htdocs) و، لە ناو index.php، کە ڕێگە بە سەردانکەران بدات هەژمار دروست بکەن. تاقیگەی 7 ئەم دەدات؛ لێرەدا بە سێ چاککردنەوە پیشان دەدرێت:
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
Username: <input type="text" name="uname" size="30"><br><br>
Email: <input type="email" name="email" size="30"><br><br>
Password: <input type="password" name="pass1" size="30"><br><br>
Confirm password: <input type="password" name="pass2" size="30"><br><br>
<input type="submit" value="Create Account" name="submit"><br>
</form>HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<form method="post" action="/php_mysql/index.php">
Username: <input type="text" name="uname" size="30"><br><br>
Email: <input type="email" name="email" size="30"><br><br>
Password: <input type="password" name="pass1" size="30"><br><br>
Confirm password: <input type="password" name="pass2" size="30"><br><br>
<input type="submit" value="Create Account" name="submit"><br>
</form>What the server sends when the page is opened as /php_mysql/index.php.
ئەوەی دەینێرێت کاتێک لاپەڕەکە وەک /php_mysql/index.php دەکرێتەوە.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 1
The form sends its data back to
index.phpitself, with POST.زانیارییەکانی بە POST دەنێرێتەوە بۆ خودی
index.php.- Lineدێڕی 3
type="email"lets the browser check the address (the lab usedtype="text").type="email"ڕێگە بە دەدات ناونیشانەکە بپشکنێت (تاقیگەکەtype="text"ی بەکار هێنا).- Lineدێڕی 5
The lab labels this second password box "Username:" by mistake; it is "Confirm password".
تاقیگەکە بە هەڵە ئەم چوارچێوەی دووەمی ناو دەنێت «Username:»؛ «Confirm password»ـە.
Lab 7: a registration page with PHP and MySQLتاقیگەی 7: لاپەڕەیەکی خۆتۆمارکردن بە PHP و MySQL
Validating the formپشتڕاستکردنەوەی فۆرمەکە
After the submit button, PHP checks the fields one after another with an if/elseif chain. Only when everything is correct does the final else save the account:
دوای ، PHP خانەکان یەک لە دوای یەک بە زنجیرەیەکی if/elseif دەپشکنێت. تەنها کاتێک هەموو شتێک دروست بێت elseـی کۆتایی هەژمارەکە هەڵدەگرێت:
| Checkپشکنین | Messageپەیام |
|---|---|
empty($_POST["uname"]) | Username is required |
empty($_POST["email"]) | Please enter your email address |
!filter_var($_POST["email"], FILTER_VALIDATE_EMAIL) | Please enter a valid email address (added)Please enter a valid email address (زیادکراوە) |
empty($_POST["pass1"]) | Please enter your password |
empty($_POST["pass2"]) | Please confirm your password |
$_POST["pass1"] != $_POST["pass2"] | The passwords did not match, please try again |
Lab 7: a registration page with PHP and MySQLتاقیگەی 7: لاپەڕەیەکی خۆتۆمارکردن بە PHP و MySQL
connection.phpفایلی connection.php
Create a new PHP file named connection.php with the code that connects the project to the php_mysql database:
فایلێکی نوێی PHP بە ناوی connection.php دروست بکە بەو کۆدەی پرۆژەکە بە php_mysqlـەوە دەبەستێتەوە:
<?php
// connection.php
try {
$connect = mysqli_connect("localhost", "root", "", "php_mysql");
mysqli_set_charset($connect, "utf8mb4");
} catch (mysqli_sql_exception $e) {
error_log($e->getMessage());
exit("Could not connect to the database.");
}(no output when the connection works)
Checked with php -l (no syntax errors). It cannot run in our sandbox because there is no MySQL server there.
بە php -l پشکنراوە (هیچ نییە). لە ژینگەی تاقیکردنەوەکەماندا ناتوانێت کار بکات چونکە MySQL لەوێ نییە.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 4
Connect and select
php_mysqlin one call. XAMPP: userroot, empty password (the lab's'root','root'is the MAMP default).پەیوەندی و هەڵبژاردنی
php_mysqlبە یەک بانگکردن. XAMPP: بەکارهێنەریroot، بەتاڵ ('root','root'ی تاقیگەکە بنەڕەتیی MAMPـە).- Lineدێڕی 6-9
PHP 8.1+ throws when the connection fails; catch it here.
PHP 8.1+ کاتێک پەیوەندییەکە شکست دەهێنێت فڕێ دەدات؛ لێرە بیگرە.
Lab 7: a registration page with PHP and MySQLتاقیگەی 7: لاپەڕەیەکی خۆتۆمارکردن بە PHP و MySQL
Reading the values and saving the accountخوێندنەوەی نرخەکان و هەڵگرتنی هەژمارەکە
Inside the final else (everything is valid), the lab does four things: read the values from the form, protect the password, connect to the database, and insert the row. Then it closes the connection.
لە ناو elseـی کۆتاییدا (هەموو شتێک دروستە)، تاقیگەکە چوار کار دەکات: نرخەکان لە دەخوێنێتەوە، دەپارێزێت، پەیوەندی بە دەکات و زیاد دەکات. پاشان پەیوەندییەکە دادەخات.
// getting values from the form inputs
$name = $_POST["uname"];
$email = $_POST["email"];
$password = password_hash($_POST["pass1"], PASSWORD_DEFAULT); // hash the password
require "./connection.php"; // connect to the database
try {
$stmt = mysqli_prepare($connect, "INSERT INTO users (name, email, password) VALUES (?, ?, ?)");
mysqli_stmt_bind_param($stmt, "sss", $name, $email, $password);
mysqli_stmt_execute($stmt);
echo "Account created successfully, you can log in now";
} catch (mysqli_sql_exception $e) {
if ($e->getCode() == 1062) { // 1062 = this value already exists in a UNIQUE column
echo "This email address already has an account";
} else {
error_log($e->getMessage());
echo "An error occurred, please try again";
}
}
// close the connection
mysqli_close($connect);Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2-4
Read the three values. The password is hashed with
password_hash()— never stored as typed.سێ نرخەکە بخوێنەوە. بە
password_hash()هاش دەکرێت — هەرگیز وەک خۆی هەڵناگیرێت.- Lineدێڕی 6
requirethe connection only now, when it is needed (it could also be at the top of the page).تەنها ئێستا، کاتێک پێویستە، پەیوەندییەکە
requireبکە (دەشکرا لە سەرەتای لاپەڕەکەدا بێت).- Lineدێڕی 9-11
A prepared INSERT with three strings. The column is
password— make sure the table does not saypasswrod(see the table lecture).INSERTێکی ئامادەکراو بە سێ .
passwordـە — دڵنیابە کەpasswrodنەنووسیبێت (وانەی خشتەکە ببینە).- Lineدێڕی 13-19
The
emailcolumn is UNIQUE, so a second account with the same email throws error 1062 (Duplicate entry).emailUNIQUE ـە، بۆیە هەژمارێکی دووەم بە هەمان هەڵەی 1062 (Duplicate entry) فڕێ دەدات.- Lineدێڕی 22
The final step: close the connection that was opened before.
دوا هەنگاو: ئەو پەیوەندییەی پێشتر کرابووەوە دابخە.
Three serious problems in the lab's versionسێ کێشەی گەورە لە وەشانەکەی تاقیگەکەدا
Lab 7: a registration page with PHP and MySQLتاقیگەی 7: لاپەڕەیەکی خۆتۆمارکردن بە PHP و MySQL
The complete index.phpindex.phpـی تەواو
All the code inside index.php: the form, the PHP block after the submit button (so messages appear under the form), the validation chain and the insert.
هەموو کۆدی ناو index.php: ، بلۆکی PHP دوای (بۆ ئەوەی پەیامەکان لە ژێر دەربکەون)، زنجیرەی پشکنین و زیادکردنەکە.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Create an account</title>
</head>
<body>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
Username: <input type="text" name="uname" size="30"><br><br>
Email: <input type="email" name="email" size="30"><br><br>
Password: <input type="password" name="pass1" size="30"><br><br>
Confirm password: <input type="password" name="pass2" size="30"><br><br>
<input type="submit" value="Create Account" name="submit"><br>
<?php
if (isset($_POST["submit"])) {
if (empty($_POST["uname"])) {
echo "Username is required";
} elseif (empty($_POST["email"])) {
echo "Please enter your email address";
} elseif (!filter_var($_POST["email"], FILTER_VALIDATE_EMAIL)) {
echo "Please enter a valid email address";
} elseif (empty($_POST["pass1"])) {
echo "Please enter your password";
} elseif (empty($_POST["pass2"])) {
echo "Please confirm your password";
} elseif ($_POST["pass1"] != $_POST["pass2"]) {
echo "The passwords did not match, please try again";
} else {
// getting values from the form inputs
$name = $_POST["uname"];
$email = $_POST["email"];
$password = password_hash($_POST["pass1"], PASSWORD_DEFAULT); // hash the password
require "./connection.php"; // connect to the database
try {
$stmt = mysqli_prepare($connect, "INSERT INTO users (name, email, password) VALUES (?, ?, ?)");
mysqli_stmt_bind_param($stmt, "sss", $name, $email, $password);
mysqli_stmt_execute($stmt);
echo "Account created successfully, you can log in now";
} catch (mysqli_sql_exception $e) {
if ($e->getCode() == 1062) { // 1062 = this value already exists in a UNIQUE column
echo "This email address already has an account";
} else {
error_log($e->getMessage());
echo "An error occurred, please try again";
}
}
// close the connection
mysqli_close($connect);
}
}
?>
</form>
</body>
</html>1. Username left empty -> Username is required 2. Email without @ -> Please enter a valid email address 3. Passwords do not match -> The passwords did not match, please try again 4. Everything correct -> Account created successfully, you can log in now 5. Same email a second time -> This email address already has an account Stored row: id=1, name=Dilan, email=dilan@example.com, password=$2y$12$... (60 characters)
Real output: the same validation and insert logic run five times with this code run against SQLite through a small mysqli test layer (a UNIQUE email column), checking each message. In SQLite a duplicate is SQLSTATE 23000; in MySQL it is error 1062.
ڕاستەقینە: هەمان لۆجیکی پشکنین و زیادکردن پێنج جار بە ئەم کۆدە لەسەر SQLite لە ڕێگەی چینێکی بچووکی تاقیکردنەوەی mysqliـەوە جێبەجێ کراوە ( UNIQUE) و هەر پەیامێک پشکنراوە. لە SQLiteدا دووبارەبوونەوە SQLSTATE 23000ـە؛ لە MySQLدا هەڵەی 1062ـە.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 8-13
The corrected form.
ڕاستکراوە.
- Lineدێڕی 14-15
The PHP block sits inside the form, after the button, and runs only after a submit.
بلۆکی PHP لە ناو ، دوای دوگمەکە و تەنها دوای ناردن جێبەجێ دەبێت.
- Lineدێڕی 16-27
The validation chain; the first problem found is reported.
زنجیرەی پشکنین؛ یەکەم کێشە کە دۆزرایەوە ڕاپۆرت دەکرێت.
- Lineدێڕی 28-51
Everything is valid: hash, connect, prepared INSERT, handle a duplicate email, close.
هەموو شتێک دروستە: هاش، پەیوەندی، INSERTـی ئامادەکراو، چارەسەرکردنی دووبارە، داخستن.
- Lineدێڕی 54-56
Unlike the lab, the page ends properly with
</body></html>.بە پێچەوانەی تاقیگەکە، لاپەڕەکە بە دروستی بە
</body></html>کۆتایی دێت.
A classmate's registration code: find the bugsکۆدی خۆتۆمارکردنی هاوپۆلێک: هەڵەکان بدۆزەرەوە
A student wrote another registration page for a table users (email, password). It connects, checks the fields, and saves the account. It looks reasonable but has five problems:
خوێندکارێک لاپەڕەیەکی تری خۆتۆمارکردنی بۆ users (email, password) نووسی. پەیوەندی دەکات، خانەکان دەپشکنێت و هەژمارەکە هەڵدەگرێت. ڕێک دیارە بەڵام پێنج کێشەی هەیە:
$connect = mysqli_connect('localhost','root','','ABCD');
if(!$connect){
die ("failed your database".mysqli_error($connect));
}
$emailError = null;
$PassError = null;
$RpassError = null;
if (isset($_POST['subbtn'])){
if (empty($_POST['email'])){
$emailError = "Require your email";
}
elseif (!strstr($_POST['email'],"@") && (!strstr($_POST['email'],"."))){
$emailError = " contain @ and .";
}
if (empty($_POST['pass'])){
$PassError = "require your password";
}
if ($_POST['rpass'] != $_POST['pass']){
$RpassError = "Mismatch your password";
}
}
if(!empty($_POST['email']) && !empty($_POST['pass'])){
$email = $_POST['email'];
$password = md5($_POST['pass']);
$sql_command = "insert into users (email,password) values ('$email' ,'$password') ";
if (mysqli_query($connect,$sql_command)){
echo "done";
}else{
echo"Failed".mysqli_error($connect);
}
}Reading rowsخوێندنەوەی تۆمارەکان
Reading is steps 3 and 4 of the five steps: run a SELECT, then use the returned rows.
خوێندنەوە هەنگاوی 3 و 4ی پێنج هەنگاوەکەیە: SELECTێک جێبەجێ بکە، پاشان گەڕاوەکان بەکار بهێنە.
Reading rowsخوێندنەوەی تۆمارەکان
SELECT and a while loopSELECT و خولێکی while
To show rows, run a SELECT, then take the rows one at a time in a while loop and print each one. Here the users of Lab 7 are shown in an :
بۆ پیشاندانی ، SELECTێک جێبەجێ بکە، پاشان لە یەک یەک وەربگرە و هەر یەکێکیان چاپ بکە. لێرەدا بەکارهێنەرانی تاقیگەی 7 لە پیشان دەدرێن:
<?php
require "connection.php"; // creates $connect (database php_mysql)
$result = mysqli_query($connect, "SELECT id, name, email FROM users ORDER BY id");
?>
<table border="1">
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
</tr>
<?php
while ($row = mysqli_fetch_assoc($result)) {
echo " <tr>\n";
echo " <td>" . htmlspecialchars($row["id"]) . "</td>\n";
echo " <td>" . htmlspecialchars($row["name"]) . "</td>\n";
echo " <td>" . htmlspecialchars($row["email"]) . "</td>\n";
echo " </tr>\n";
}
?>
</table>
<p><?php echo mysqli_num_rows($result); ?> users</p>HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<table border="1">
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
</tr>
<tr>
<td>1</td>
<td>Dilan</td>
<td>dilan@example.com</td>
</tr>
<tr>
<td>2</td>
<td>Rezan</td>
<td>rezan@example.com</td>
</tr>
<tr>
<td>3</td>
<td>Hana <b></td>
<td>hana@example.com</td>
</tr>
</table>
<p>3 users</p>Real output (this code run against SQLite through a small mysqli test layer) with three users. The third user typed "Hana <b>" as a name: thanks to htmlspecialchars() it is shown as text, not turned into bold HTML.
ڕاستەقینە (ئەم کۆدە لەسەر SQLite لە ڕێگەی چینێکی بچووکی تاقیکردنەوەی mysqliـەوە) بە سێ بەکارهێنەر. بەکارهێنەری سێیەم «Hana <b>»ی وەک ناو نووسیوە: بەهۆی htmlspecialchars() وەک دەق پیشان دەدرێت، نەک ببێت بە HTMLـی تۆخ.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 3
No visitor input in this query, so
mysqli_query()is fine. Choose the columns you need — never show the password column.هیچ نێوخستەیەکی سەردانکەر لەم پرسەدا نییە، بۆیە
mysqli_query()باشە. ئەو ستوونانە هەڵبژێرە کە پێویستتن — هەرگیز پیشان مەدە.- Lineدێڕی 12
Each call returns the next row as an associative array; at the end it returns
nulland the loop stops.هەر بانگکردنێک دواتر وەک دەگەڕێنێتەوە؛ لە کۆتاییدا
nullدەگەڕێنێتەوە و ڕادەوەستێت.- Lineدێڕی 13-17
One table row
<tr>per database row, opened and closed inside the loop. Every value goes throughhtmlspecialchars().یەک
<tr>بۆ هەر ، لە ناو دەکرێتەوە و دادەخرێت. هەموو نرخێک بەhtmlspecialchars()دا تێدەپەڕێت.- Lineدێڕی 21
mysqli_num_rows()counts the rows of the result.mysqli_num_rows()ئەنجامەکە دەژمێرێت.
How to create a function, send values in with arguments, and get a result back with return, with a short practice example.
چۆن دروست بکەیت، بە بنێریتە ناوی و بە return ئەنجامێک لێی وەربگریتەوە، لەگەڵ ڕاهێنانێکی کورت.
Watch 3:45:00–3:49:37: SELECT, mysqli_num_rows and a while loop
لە 3:45:00 تا 3:49:37 سەیر بکە: SELECT، mysqli_num_rows() و .
Reading rowsخوێندنەوەی تۆمارەکان
What "fetch" means, and the three result modesواتای «fetch» و سێ شێوازی ئەنجام
To means to go and bring back data from a source — a database, a file or a web service. With MySQLi it means taking a row (or rows) out of a query's . mysqli_fetch_array() fetches one row and returns it as an array. It has two arguments: the result set, and an optional constant that chooses the array type. Each call fetches the next row; when there are no more rows it returns null (and false on a failure).
واتە چوون و هێنانەوەی زانیاری لە سەرچاوەیەکەوە — ، فایل یان . لە MySQLiدا واتە دەرهێنانی (یان چەند ) لە . mysqli_fetch_array() یەک دەهێنێت و وەک دەیگەڕێنێتەوە. دوو هەیە: و ئارەزوومەندانە کە جۆری هەڵدەبژێرێت. هەر بانگکردنێک دواتر دەهێنێت؛ کاتێک نەما null دەگەڕێنێتەوە (و لە کاتی شکستدا false).
| Mode constant شێواز | Keys in the arrayکلیلەکانی ناو | Same asهەمان وەک |
|---|---|---|
MYSQLI_BOTH | numbers and column names (the default)ژمارە و ناوی (بنەڕەتی) | mysqli_fetch_array($result) |
MYSQLI_ASSOC | column names onlyتەنها ناوی | mysqli_fetch_assoc($result) |
MYSQLI_NUM | numbers only (0, 1, 2, ...)تەنها ژمارە (0، 1، 2، ...) | mysqli_fetch_row($result) |
The modes in codeشێوازەکان لە کۆددا
<?php
require "connection.php"; // the school database of the previous lecture: it creates $con
$result = mysqli_query($con, "SELECT sId, sName, Marks FROM result");
$row = mysqli_fetch_array($result); // MYSQLI_BOTH (default): numbers AND names
echo $row[1] . " = " . $row["sName"] . "<br>"; // both keys work
$row = mysqli_fetch_array($result, MYSQLI_ASSOC); // names only, like mysqli_fetch_assoc()
echo $row["sName"] . "<br>";
$row = mysqli_fetch_array($result, MYSQLI_NUM); // numbers only, like mysqli_fetch_row()
echo $row[1] . "<br>";Aram = Aram Shilan Karwan
HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
Aram = Aram<br>Shilan<br>Karwan<br>Real output with the three rows of the result table (Aram, Shilan, Karwan). This exact code was run against SQLite through a small mysqli test layer; the PDO version below runs as it is.
ڕاستەقینە بە سێ result (Aram، Shilan، Karwan). هەمان ئەم کۆدە لەسەر SQLite لە ڕێگەی چینێکی بچووکی تاقیکردنەوەی mysqliـەوە جێبەجێ کرا؛ وەشانی PDOـی خوارەوە وەک خۆی کار دەکات.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 5-6
Default mode:
$row[1]and$row["sName"]both give "Aram".شێوازی بنەڕەتی:
$row[1]و$row["sName"]هەردووکیان «Aram» دەدەن.- Lineدێڕی 8
Names only, for the second row.
تەنها ناو، بۆ دووەم.
- Lineدێڕی 11
Numbers only, for the third row.
تەنها ژمارە، بۆ سێیەم.
<?php
// The three result modes, shown with PDO + SQLite (PDO uses FETCH_BOTH / FETCH_ASSOC / FETCH_NUM)
$db = new PDO("sqlite::memory:");
$db->exec("CREATE TABLE result (sId INT, sName TEXT, Marks INT)");
$db->exec("INSERT INTO result VALUES (1, 'Aram', 85), (2, 'Shilan', 92), (3, 'Karwan', 78)");
$stmt = $db->query("SELECT sId, sName, Marks FROM result");
echo "BOTH: "; print_r($stmt->fetch(PDO::FETCH_BOTH));
echo "ASSOC: "; print_r($stmt->fetch(PDO::FETCH_ASSOC));
echo "NUM: "; print_r($stmt->fetch(PDO::FETCH_NUM));
echo "No rows left: "; var_dump($stmt->fetch());BOTH: Array
(
[sId] => 1
[0] => 1
[sName] => Aram
[1] => Aram
[Marks] => 85
[2] => 85
)
ASSOC: Array
(
[sId] => 2
[sName] => Shilan
[Marks] => 92
)
NUM: Array
(
[0] => 3
[1] => Karwan
[2] => 78
)
No rows left: bool(false)
Real output. PDO names the modes FETCH_BOTH, FETCH_ASSOC and FETCH_NUM, and returns false (mysqli: null) when no rows are left.
ڕاستەقینە. PDO شێوازەکان ناو دەنێت FETCH_BOTH، FETCH_ASSOC و FETCH_NUM و کاتێک نەما false دەگەڕێنێتەوە (mysqli: null).
The notes' example uses the default mode in a loop. Corrected so that the values are escaped:
نموونەی تێبینییەکان شێوازی بنەڕەتی لە بەکار دەهێنێت. ڕاستکراوەتەوە بۆ ئەوەی نرخەکان پاک بکرێنەوە:
<?php
$conn = mysqli_connect("localhost", "username", "password", "database");
$result = mysqli_query($conn, "SELECT id, name FROM mytable");
// Each call fetches the next row; at the end it returns null and the loop stops
while ($row = mysqli_fetch_array($result)) {
echo htmlspecialchars($row["id"]) . " - " . htmlspecialchars($row["name"]) . "<br>";
}
mysqli_close($conn);1 - Aram<br>2 - Shilan<br>...
Checked with php -l (no syntax errors). It cannot run in our sandbox because there is no MySQL server there.
بە php -l پشکنراوە (هیچ نییە). لە ژینگەی تاقیکردنەوەکەماندا ناتوانێت کار بکات چونکە MySQL لەوێ نییە.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2
Example login details: put your own host, user, password and database here.
زانیاریی نموونەیی بۆ پەیوەندی: هۆست، بەکارهێنەر، و خۆت لێرە دابنێ.
- Lineدێڕی 7
The loop runs once per row;
mysqli_fetch_array()returnsnullafter the last row.بۆ هەر یەک جار دەخولێتەوە؛
mysqli_fetch_array()دوای دواnullدەگەڕێنێتەوە.
Reading rowsخوێندنەوەی تۆمارەکان
Counting rows: mysqli_num_rows()ژماردنی تۆمارەکان: mysqli_num_rows()
mysqli_num_rows($result) tells you how many rows a SELECT returned. In object style the same number is the property $result->num_rows (no brackets).
mysqli_num_rows($result) پێت دەڵێت SELECTێک چەند گەڕاندووەتەوە. لە شێوازی هەمان ژمارە $result->num_rowsـە (بێ کەوانە).
<?php
$conn = mysqli_connect("localhost", "username", "password", "database_name");
$result = mysqli_query($conn, "SELECT * FROM users");
$num_rows = mysqli_num_rows($result); // procedural style
// $num_rows = $result->num_rows; // object style: the same number
echo "There are " . $num_rows . " rows in the users table.";There are 3 rows in the users table.
Checked with php -l (no syntax errors). It cannot run in our sandbox because there is no MySQL server there.
بە php -l پشکنراوە (هیچ نییە). لە ژینگەی تاقیکردنەوەکەماندا ناتوانێت کار بکات چونکە MySQL لەوێ نییە.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 6
The function: it takes the result.
: ئەنجامەکە وەردەگرێت.
- Lineدێڕی 7
The property: the same number.
: هەمان ژمارە.
Use mysqli_num_rows() when you fetch the rows anyway (for example to print "No record found"). If you only need the number, let the database count with COUNT(*): only one small number travels back instead of every row.
mysqli_num_rows() بەکار بهێنە کاتێک بەهەرحاڵ دەهێنیت (بۆ نموونە بۆ چاپکردنی «No record found»). ئەگەر تەنها ژمارەکەت پێویستە، بهێڵە بە COUNT(*) بیژمێرێت: تەنها یەک ژمارەی بچووک دەگەڕێتەوە لە جیاتی هەموو .
<?php
$db = new PDO("sqlite::memory:");
$db->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, city TEXT)");
$db->exec("INSERT INTO users (name, city) VALUES ('Dilan','Erbil'), ('Rezan','Duhok'), ('Hana','Erbil'), ('Soran','Erbil')");
// 1. Fetch the rows, then count them in PHP (like mysqli_num_rows)
$rows = $db->query("SELECT * FROM users WHERE city = 'Erbil'")->fetchAll();
echo "Rows fetched: " . count($rows) . "\n";
// 2. Let the database count: only one small number travels back
$total = $db->query("SELECT COUNT(*) FROM users")->fetchColumn();
echo "COUNT(*): " . $total . "\n";Rows fetched: 3 COUNT(*): 4
Real output (SQLite).
ڕاستەقینە (SQLite).
One page: check, insert with an image, and listیەک لاپەڕە: پشکنین، زیادکردن لەگەڵ وێنە و لیستکردن
The practical notes contain a bigger page that does everything at once. The table users has id, username, password and filename (the name of an uploaded image). When the form is submitted the page:
تێبینییە پراکتیکییەکان لاپەڕەیەکی گەورەتریان تێدایە کە هەموو شتێک پێکەوە دەکات. users id، username، password و filename (ناوی وێنەیەکی بارکراو)ی هەیە. کاتێک دەنێردرێت، لاپەڕەکە:
- checks whether the username already exists (a
SELECTwithWHERE username = ...); - if it is new, checks the uploaded image (not empty, not already on the server, only
pngorjpg), saves the user and moves the file intouploads/; - lists all users in a table and writes already exists in red next to the name that was tried again;
- shows an insert form in the last table row.
- دەپشکنێت ئایا پێشتر هەیە (
SELECTێک بەWHERE username = ...)؛ - ئەگەر نوێ بێت، وێنە بارکراوەکە دەپشکنێت (بەتاڵ نەبێت، پێشتر لەسەر نەبێت، تەنها
pngیانjpg)، بەکارهێنەرەکە هەڵدەگرێت و فایلەکە دەگوازێتەوە بۆuploads/؛ - هەموو بەکارهێنەران لە لیست دەکات و لە تەنیشت ئەو ناوەی دووبارە تاقی کرایەوە already exists بە سوور دەنووسێت؛
- لە دوا زیادکردن پیشان دەدات.
Here is that page rewritten safely. The SELECT that checks the username uses visitor input, so it is a prepared statement too; its result is read with mysqli_stmt_get_result().
ئەمە ئەو لاپەڕەیەیە کە بە سەلامەتی دووبارە نووسراوەتەوە. ئەو SELECTـەی دەپشکنێت نێوخستەی سەردانکەر بەکار دەهێنێت، بۆیە ئەویش ؛ ئەنجامەکەی بە mysqli_stmt_get_result() دەخوێندرێتەوە.
<?php
$con = mysqli_connect("localhost", "root", "", "MyStudy"); // throws if it fails (PHP 8.1+)
mysqli_set_charset($con, "utf8mb4");
$alreadyExists = false;
$username = "";
$message = "";
if (isset($_POST["SubBtn"])) {
$username = trim($_POST["userInput"]);
if ($username === "") {
$message = "Username cannot be empty";
} else {
// 1. Does this username already exist? (prepared SELECT)
$stmt = mysqli_prepare($con, "SELECT id FROM users WHERE username = ?");
mysqli_stmt_bind_param($stmt, "s", $username);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
$alreadyExists = mysqli_num_rows($result) > 0;
}
if ($username !== "" && !$alreadyExists) {
// 2. New user: check the uploaded image
$file_name = basename($_FILES["FileName"]["name"]); // no folder names from the visitor
$file_tmp = $_FILES["FileName"]["tmp_name"];
$target_file = "uploads/" . $file_name;
$parts = explode(".", $file_name);
$extension = strtolower(end($parts));
$allowed = ["png", "jpg"];
if ($file_name === "") {
$message = "Image input cannot be empty";
} elseif (file_exists($target_file)) {
$message = "Image file already exists.";
} elseif (!in_array($extension, $allowed)) {
$message = "Only PNG and JPG images are allowed.";
} else {
// 3. Save the user (prepared INSERT, hashed password), then keep the file
$hash = password_hash($_POST["passInput"], PASSWORD_DEFAULT);
$stmt = mysqli_prepare($con, "INSERT INTO users (username, password, filename) VALUES (?, ?, ?)");
mysqli_stmt_bind_param($stmt, "sss", $username, $hash, $file_name);
mysqli_stmt_execute($stmt);
move_uploaded_file($file_tmp, $target_file);
$message = "Success: the user was added.";
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Users</title>
</head>
<body>
<p><?php echo htmlspecialchars($message); ?></p>
<form method="post" enctype="multipart/form-data">
<table border="1">
<tr>
<th>ID</th>
<th>Username</th>
<th>Image</th>
</tr>
<?php
$selectQuery = mysqli_query($con, "SELECT id, username, filename FROM users ORDER BY id");
while ($row = mysqli_fetch_assoc($selectQuery)) {
?>
<tr>
<td><?php echo $row["id"]; ?></td>
<td>
<?php echo htmlspecialchars($row["username"]);
if ($alreadyExists && $row["username"] === $username) {
echo ' <span style="color:red">already exists</span>';
} ?>
</td>
<td><img width="50" height="60" alt="" src="uploads/<?php echo htmlspecialchars($row["filename"]); ?>"></td>
</tr>
<?php
}
?>
<tr>
<td></td>
<td>
<input type="text" name="userInput" placeholder="username">
<input type="password" name="passInput" placeholder="password">
</td>
<td>
<input type="file" name="FileName">
<input type="submit" name="SubBtn" value="Insert">
</td>
</tr>
</table>
</form>
</body>
</html>1 Dilan already exists [image] 2 Rezan [image]
HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<tr>
<td>1</td>
<td>
Dilan <span style="color:red">already exists</span> </td>
<td><img width="50" height="60" alt="" src="uploads/dilan.png"></td>
</tr>
<tr>
<td>2</td>
<td>
Rezan </td>
<td><img width="50" height="60" alt="" src="uploads/rezan.jpg"></td>
</tr>Real output of the duplicate check and the table rows (this code run against SQLite through a small mysqli test layer, no upload), after trying the existing name "Dilan".
ڕاستەقینەی پشکنینی دووبارە و ڕیزەکانی (ئەم کۆدە لەسەر SQLite لە ڕێگەی چینێکی بچووکی تاقیکردنەوەی mysqliـەوە، بێ بارکردن)، دوای تاقیکردنەوەی ناوی هەبووی «Dilan».
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 2-3
Connect (the notes used database
MyStudy).پەیوەندی بکە (تێبینییەکان
MyStudyیان بەکار هێنا).- Lineدێڕی 16-20
A prepared SELECT;
mysqli_stmt_get_result()gives a normal result, andmysqli_num_rows() > 0means the name is taken.SELECTـێکی ئامادەکراو؛
mysqli_stmt_get_result()ئەنجامێکی ئاسایی دەدات وmysqli_num_rows() > 0واتە ناوەکە گیراوە.- Lineدێڕی 25
basename()removes any folder part from the uploaded file name.basename()هەر بەشێکی فۆڵدەر لە ناوی فایلە بارکراوەکە لادەبات.- Lineدێڕی 32-37
The same three image checks as the notes: empty, already exists, allowed extension. More on uploads: File uploads.
هەمان سێ پشکنینی وێنەی تێبینییەکان: بەتاڵ، پێشتر هەیە، پاشگری ڕێگەپێدراو. زیاتر دەربارەی بارکردن: بارکردنی فایل.
- Lineدێڕی 40-43
Hash the password; a prepared INSERT with three strings.
هاش بکە؛ INSERTـێکی ئامادەکراو بە سێ .
- Lineدێڕی 59-60
The
<form>wraps the whole table (a form directly inside<tr>is invalid HTML).<form>هەموو دەگرێتەوە ( ڕاستەوخۆ لە ناو<tr>دا HTMLـی نادروستە).- Lineدێڕی 68-81
One complete
<tr>...</tr>per user. The password column is not shown.یەک
<tr>...</tr>ـی تەواو بۆ هەر بەکارهێنەرێک. پیشان نادرێت.- Lineدێڕی 74-76
Mark the name that was tried again in red.
ئەو ناوەی دووبارە تاقی کرایەوە بە سوور نیشان بکە.
One page: check, insert with an image, and listیەک لاپەڕە: پشکنین، زیادکردن لەگەڵ وێنە و لیستکردن
What was wrong in the notes' versionچی هەڵە بوو لە وەشانەکەی تێبینییەکاندا
Long lists: LIMIT, OFFSET and paginationلیستە درێژەکان: LIMIT، OFFSET و لاپەڕەبەندی
MySQL provides a that sets the maximum number of records a SELECT returns. Returning a large number of records can slow a page down, so LIMIT is very useful on large tables. Together with OFFSET, which skips a number of rows first, it makes it easy to show multi-page results: .
MySQL دابین دەکات کە زۆرترین ژمارەی ئەو تۆمارانە دیاری دەکات کە SELECTێک دەیانگەڕێنێتەوە. گەڕاندنەوەی ژمارەیەکی زۆر دەتوانێت لاپەڕەیەک هێواش بکات، بۆیە LIMIT لەسەر گەورەکان زۆر بەسوودە. لەگەڵ OFFSET، کە سەرەتا ژمارەیەک تێدەپەڕێنێت، پیشاندانی ئەنجام لە چەند لاپەڕەیەکدا ئاسان دەکات: .
Long lists: LIMIT, OFFSET and paginationلیستە درێژەکان: LIMIT، OFFSET و لاپەڕەبەندی
LIMIT and OFFSET in SQLLIMIT و OFFSET لە SQLدا
The course examples, run on a table Orders with ids 1 to 50 (an ORDER BY id is added so that "first" always means the same rows):
نموونەکانی وانەکە، لەسەر Orders بە id ـی 1 تا 50 جێبەجێ کراون (ORDER BY id زیاد کراوە بۆ ئەوەی «یەکەم» هەمیشە هەمان بێت):
SELECT * FROM Orders ORDER BY id LIMIT 30;30 rows: ids 1 to 30
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 1
At most 30 rows: the first 30.
زۆرترین 30 : 30ی یەکەم.
SELECT * FROM Orders ORDER BY id LIMIT 10 OFFSET 15;10 rows: ids 16 to 25
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 1
Skip 15 rows, then return 10: rows 16 to 25.
15 تێبپەڕێنە، پاشان 10 بگەڕێنەوە: 16 تا 25.
SELECT * FROM Orders ORDER BY id LIMIT 15, 10;10 rows: ids 16 to 25
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 1
MySQL's short form:
LIMIT offset, count— careful, here the offset comes first. Same result as the previous query.شێوە کورتەکەی MySQL:
LIMIT offset, count— ئاگاداربە، لێرەدا offset یەکەمە. هەمان ئەنجامی پێشوو.
The student notes give three more examples on a table my_table:
تێبینییەکانی خوێندکار سێ نموونەی تر لەسەر my_table دەدەن:
| Query | Resultئەنجام |
|---|---|
SELECT * FROM my_table LIMIT 10; | the first 10 rows (rows 1–10)10 یەکەم ( 1–10) |
SELECT * FROM my_table LIMIT 10 OFFSET 5; | 10 rows starting from the 6th row: the first 5 are skipped (rows 6–15)10 لە شەشەمەوە: 5ی یەکەم تێدەپەڕێنرێن ( 6–15) |
SELECT * FROM my_table LIMIT 10 OFFSET 10; | skip the first 10, return the next 10 (rows 11–20)10ی یەکەم تێبپەڕێنە، 10ی دواتر بگەڕێنەوە ( 11–20) |
The summary shows a picture of a Customers table and the query SELECT first_name, last_name FROM Customers LIMIT 2 OFFSET 3;. With five customers it skips three and returns the 4th and 5th:
پوختەکە وێنەی Customers و SELECT first_name, last_name FROM Customers LIMIT 2 OFFSET 3; پیشان دەدات. بە پێنج کڕیار، سێیان تێدەپەڕێنێت و چوارەم و پێنجەم دەگەڕێنێتەوە:
customer_id | first_name | last_name | age | country |
|---|---|---|---|---|
| 1 | Aram | Ali | 31 | USA |
| 2 | Shilan | Omar | 22 | USA |
| 3 | Karwan | Aziz | 22 | UK |
| 4 | Dilan | Hasan | 25 | UK |
| 5 | Rezan | Salih | 28 | UAE |
SELECT first_name, last_name FROM Customers ORDER BY customer_id LIMIT 2 OFFSET 3;Dilan Hasan Rezan Salih
Checked on SQLite.
لەسەر SQLite پشکنراوە.
Shows with clear animations how LIMIT and OFFSET split a long list into pages, and why ORDER BY is needed.
بە ڕوون پیشان دەدات چۆن و لیستێکی درێژ دابەشی چەند لاپەڕەیەک دەکەن و بۆچی پێویستە.
Watch 1:25–6:12: ORDER BY, then LIMIT and OFFSET pagination
لە 1:25 تا 6:12 سەیر بکە: ، پاشان بە LIMIT و OFFSET.
Long lists: LIMIT, OFFSET and paginationلیستە درێژەکان: LIMIT، OFFSET و لاپەڕەبەندی
Pagination in PHPلاپەڕەبەندی لە PHPدا
For page n with k rows per page: OFFSET = (n − 1) × k. Page 1 skips 0 rows, page 2 skips k, page 3 skips 2k. The page number arrives in the URL (?page=3), so it is visitor input: turn it into an integer and send it with a prepared statement.
بۆ لاپەڕەی n بە k بۆ هەر لاپەڕەیەک: OFFSET = (n − 1) × k. لاپەڕەی 1 هیچ تێناپەڕێنێت، لاپەڕەی 2 k تێدەپەڕێنێت، لاپەڕەی 3 دوو هێندەی k. ژمارەی لاپەڕە لە URLـدا دێت (?page=3)، بۆیە : بیکە بە و بە بینێرە.
<?php
require "dbcon.php";
$perPage = 10; // rows on one page
$page = isset($_GET["page"]) ? (int) $_GET["page"] : 1; // ?page=3 in the URL
if ($page < 1) {
$page = 1;
}
$offset = ($page - 1) * $perPage; // page 3 -> skip 20 rows
$stmt = mysqli_prepare($con, "SELECT id, product FROM Orders ORDER BY id LIMIT ? OFFSET ?");
mysqli_stmt_bind_param($stmt, "ii", $perPage, $offset); // both are integers
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
while ($row = mysqli_fetch_assoc($result)) {
echo $row["id"] . ": " . htmlspecialchars($row["product"]) . "<br>\n";
}
echo '<a href="?page=' . ($page + 1) . '">Next page</a>';21: Item 21 22: Item 22 ... 30: Item 30 Next page
For ?page=3 on a table with 50 orders. This exact code was run against SQLite through a small mysqli test layer; the PDO version below runs as it is.
بۆ ?page=3 لەسەر کە 50 داواکاریی تێدایە. هەمان ئەم کۆدە لەسەر SQLite لە ڕێگەی چینێکی بچووکی تاقیکردنەوەی mysqliـەوە جێبەجێ کرا؛ وەشانی PDOـی خوارەوە وەک خۆی کار دەکات.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 5-8
(int)turns any text into a whole number; pages below 1 become 1.(int)هەر دەقێک دەکات بە ؛ لاپەڕەی کەمتر لە 1 دەبێت بە 1.- Lineدێڕی 9
The formula: page 3 → (3 − 1) × 10 = 20 rows skipped.
هاوکێشەکە: لاپەڕەی 3 ← (3 − 1) × 10 = 20 تێدەپەڕێنرێن.
- Lineدێڕی 11-14
LIMIT and OFFSET values are sent as integers (
"ii") through a prepared statement.نرخەکانی LIMIT و OFFSET وەک (
"ii") لە ڕێگەی دەنێردرێن.- Lineدێڕی 19
A link to the next page.
بەستەرێک بۆ لاپەڕەی دواتر.
<?php
// The same page with PDO (run here with SQLite and ?page=3)
$_GET["page"] = "3";
$pdo = new PDO("sqlite::memory:");
$pdo->exec("CREATE TABLE Orders (id INTEGER PRIMARY KEY, product TEXT)");
for ($i = 1; $i <= 50; $i++) { $pdo->exec("INSERT INTO Orders VALUES ($i, 'Item $i')"); }
$perPage = 10;
$page = isset($_GET["page"]) ? (int) $_GET["page"] : 1;
if ($page < 1) {
$page = 1;
}
$offset = ($page - 1) * $perPage;
$stmt = $pdo->prepare("SELECT id, product FROM Orders ORDER BY id LIMIT :lim OFFSET :off");
$stmt->bindValue(":lim", $perPage, PDO::PARAM_INT); // send as numbers, not text
$stmt->bindValue(":off", $offset, PDO::PARAM_INT);
$stmt->execute();
foreach ($stmt as $row) {
echo $row["id"] . ": " . htmlspecialchars($row["product"]) . "<br>\n";
}
echo '<a href="?page=' . ($page + 1) . '">Next page</a>';21: Item 21 22: Item 22 23: Item 23 24: Item 24 25: Item 25 26: Item 26 27: Item 27 28: Item 28 29: Item 29 30: Item 30 Next page
HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
21: Item 21<br>
22: Item 22<br>
23: Item 23<br>
24: Item 24<br>
25: Item 25<br>
26: Item 26<br>
27: Item 27<br>
28: Item 28<br>
29: Item 29<br>
30: Item 30<br>
<a href="?page=4">Next page</a>Real output (SQLite, 50 orders, page 3): rows 21–30.
ڕاستەقینە (SQLite، 50 داواکاری، لاپەڕەی 3): 21–30.
Explain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 16-17
With PDO, bind LIMIT/OFFSET with
PDO::PARAM_INT. If they are sent as text, MySQL rejectsLIMIT '10'.لە PDOدا LIMIT/OFFSET بە
PDO::PARAM_INTببەستەوە. ئەگەر وەک دەق بنێردرێن، MySQLLIMIT '10'ڕەت دەکاتەوە.
Common mistakesهەڵە باوەکان
| Mistakeهەڵە | What happensچی ڕوو دەدات | Fixچارەسەر |
|---|---|---|
VALUES ('$name', '$email') | SQL injection; O'Brien breaks the query؛ O'Brien تێک دەدات | Prepared statement with ? بە ? |
VALUES ('?', '?') | Quotes turn the markers into plain textکۆتەیشنەکان نیشانەکان دەکەن بە دەقی ئاسایی | Write ? without quotes? بەبێ کۆتەیشن بنووسە |
mysqli_stmt_bind_param($stmt, "s", $a, $b) | ArgumentCountError: the type string needs one letter per valueArgumentCountError: زنجیرەی جۆر بۆ هەر نرخێک یەک پیتی پێویستە | "ss" |
sha1($password) / md5($password) | Weak hashes that can be looked upهاشی لاواز کە دەکرێت بدۆزرێنەوە | password_hash() + password_verify() |
mysql_error() | Call to undefined function mysql_error() | try/catch, or mysqli_error($con)try/catch، یان mysqli_error($con) |
Column passwrod in the table passwrod لە | Unknown column 'password' in 'field list' | Rename the columnناوی بگۆڕە |
mysqli_num_rows($result <= 0) | TypeError (wrong brackets)TypeError (کەوانەی هەڵە) | mysqli_num_rows($result) <= 0 |
echo $row["name"]; | XSS if the name contains HTMLXSS ئەگەر ناوەکە HTMLـی تێدا بێت | echo htmlspecialchars($row["name"]); |
LIMIT 10 OFFSET 15 (no ORDER BY) | Pages may change or repeat rowsلاپەڕەکان لەوانەیە بگۆڕێن یان دووبارە بکەنەوە | ORDER BY id LIMIT 10 OFFSET 15 |
LIMIT 15, 10 | Read as "15 rows from row 10" — wrongوا دەخوێندرێتەوە «15 لە 10ـەوە» — هەڵەیە | Offset first: 10 rows after skipping 15سەرەتا offset: 10 دوای تێپەڕاندنی 15 |
Exercisesڕاهێنانەکان
Do Lab 7 completely: with the php_mysql database and users table, build index.php (registration form, validation, insert) and connection.php, so that a visitor can create an account. Test it with: an empty username, two different passwords, a correct registration, and the same email a second time. Check the new row in phpMyAdmin's Browse tab.
تاقیگەی 7 بە تەواوی بکە: بە php_mysql و users، index.php ( خۆتۆمارکردن، پشکنین، زیادکردن) و connection.php دروست بکە، بۆ ئەوەی سەردانکەرێک بتوانێت هەژمار دروست بکات. بەمانە تاقی بکەرەوە: بەتاڵ، دوو جیاواز، خۆتۆمارکردنێکی دروست و هەمان بۆ جاری دووەم. نوێیەکە لە تابی Browseـی phpMyAdmin بپشکنە.
Hintئاماژە
Use the corrected files in Lab 7. In Browse, the password column must start with $2y$.
فایلە ڕاستکراوەکانی تاقیگەی 7 بەکار بهێنە. لە Browseدا، دەبێت بە $2y$ دەست پێ بکات.
Show answerوەڵامەکە پیشان بدە
The complete solution is connection.php plus index.php. The four tests should print, in order: Username is required; The passwords did not match, please try again; Account created successfully, you can log in now; This email address already has an account. In phpMyAdmin the new row shows the name, the email and a 60-character hash that starts with $2y$, never the password itself.
چارەسەرە تەواوەکە connection.php لەگەڵ index.phpـە. چوار تاقیکردنەوەکە دەبێت بە ڕیز ئەمانە چاپ بکەن: Username is required؛ The passwords did not match, please try again؛ Account created successfully, you can log in now؛ This email address already has an account. لە phpMyAdminدا نوێیەکە ناو، و هاشێکی 60 پیتی پیشان دەدات کە بە $2y$ دەست پێ دەکات، هەرگیز خودی نا.
Write a page staff.php that shows the rows of the stage_4 table (id, name, age, salary) in an HTML table, sorted by salary from high to low, and prints "No record found" when the table is empty.
لاپەڕەیەکی staff.php بنووسە کە stage_4 (id، name، age، salary) لە پیشان بدات، بەپێی مووچە لە بەرزەوە بۆ نزم ڕیزکراو و کاتێک بەتاڵە «No record found» چاپ بکات.
Show answerوەڵامەکە پیشان بدە
<?php
require "dbcon.php";
$result = mysqli_query($con, "SELECT id, name, age, salary FROM stage_4 ORDER BY salary DESC");
if (mysqli_num_rows($result) === 0) {
echo "<p>No record found</p>";
} else {
echo "<table border=\"1\">\n<tr><th>ID</th><th>Name</th><th>Age</th><th>Salary</th></tr>\n";
while ($row = mysqli_fetch_assoc($result)) {
echo "<tr><td>" . $row["id"] . "</td><td>" . htmlspecialchars($row["name"]) . "</td><td>"
. $row["age"] . "</td><td>" . $row["salary"] . "</td></tr>\n";
}
echo "</table>";
}ID Name Age Salary 3 Karwan 31 520 2 Shilan 25 450 1 Aram 22 300
Real result when the table holds only the three rows from the phpMyAdmin lecture; with an empty table the page prints "No record found". Run against SQLite through a small mysqli test layer.
ئەنجامی ڕاستەقینە کاتێک تەنها سێ وانەی phpMyAdminی تێدایە؛ ئەگەر بەتاڵ بێت لاپەڕەکە «No record found» چاپ دەکات. لەسەر SQLite لە ڕێگەی چینێکی بچووکی تاقیکردنەوەی mysqliـەوە جێبەجێ کرا.
Add a search box to staff.php: the visitor types a name (?q=... in the URL) and the page shows only the rows whose name starts with that text. Use a prepared statement.
خانەیەکی گەڕان زیاد بکە بۆ staff.php: سەردانکەر ناوێک دەنووسێت (?q=... لە URLـدا) و لاپەڕەکە تەنها ئەو پیشان دەدات کە ناوەکەیان بەو دەقە دەست پێ دەکات. بەکار بهێنە.
Hintئاماژە
WHERE name LIKE ? and bind the value $q . "%".
WHERE name LIKE ? و نرخی $q . "%" ببەستەوە.
Show answerوەڵامەکە پیشان بدە
<?php
require "dbcon.php";
$q = isset($_GET["q"]) ? trim($_GET["q"]) : "";
$pattern = $q . "%"; // starts with ...
$stmt = mysqli_prepare($con, "SELECT name, salary FROM stage_4 WHERE name LIKE ? ORDER BY name");
mysqli_stmt_bind_param($stmt, "s", $pattern);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
while ($row = mysqli_fetch_assoc($result)) {
echo htmlspecialchars($row["name"]) . ": " . $row["salary"] . "<br>";
}(for ?q=Sh) Shilan: 450
Real output for ?q=Sh on the three phpMyAdmin rows; the crafted search x' OR '1'='1 finds nothing, because it is only a value. Run against SQLite through a small mysqli test layer.
ڕاستەقینە بۆ ?q=Sh لەسەر سێ phpMyAdmin؛ گەڕانی دەستکردی x' OR '1'='1 هیچ نادۆزێتەوە، چونکە تەنها . لەسەر SQLite لە ڕێگەی چینێکی بچووکی تاقیکردنەوەی mysqliـەوە جێبەجێ کرا.
The % is added in PHP, not typed into the SQL, so the visitor's text is still only a value.
% لە PHPـدا زیاد دەکرێت، نەک لە ناو SQLـدا بنووسرێت، بۆیە دەقی سەردانکەر هێشتا تەنها نرخێکە.
A shop shows 8 products per page. Which LIMIT and OFFSET does page 5 need? Which rows does it show (with ORDER BY id, ids 1, 2, 3, ...)?
فرۆشگایەک 8 کاڵا لە هەر لاپەڕەیەکدا پیشان دەدات. لاپەڕەی 5 پێویستی بە کام LIMIT و OFFSET هەیە؟ کام پیشان دەدات (بە ORDER BY id، id ـی 1، 2، 3، ...)؟
Show answerوەڵامەکە پیشان بدە
LIMIT 8 OFFSET 32, because OFFSET = (5 − 1) × 8 = 32. It shows the rows with ids 33 to 40. In MySQL's short form: LIMIT 32, 8.
LIMIT 8 OFFSET 32، چونکە OFFSET = (5 − 1) × 8 = 32. id ـی 33 تا 40 پیشان دەدات. بە شێوە کورتەکەی MySQL: LIMIT 32, 8.
A student's first page that reads a table, members (id, first_name, last_name, city), looks like this. It runs and shows the names, but it has at least six problems. Find them, then write a correct version. (One member typed Hana <b> as a first name.)
یەکەم لاپەڕەی خوێندکارێک کە دەخوێنێتەوە، members (id, first_name, last_name, city)، بەم شێوەیەیە. کار دەکات و ناوەکان پیشان دەدات، بەڵام لانیکەم شەش کێشەی هەیە. بیاندۆزەرەوە، پاشان وەشانێکی دروست بنووسە. (ئەندامێک Hana <b>ـی وەک ناوی یەکەم نووسیوە.)
<?php include('includes/config.php'); ?>
<style> td { padding: 50px; border: 1px solid black; } </style>
<?php
$sql = mysqli_query($db, "SELECT * FROM members");
while ($row = mysqli_fetch_assoc($sql)) { ?>
<table><td><?php echo $row['first_name']; ?></td></table>
<?php } ?>HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<style> td { padding: 50px; border: 1px solid black; } </style>
<table><td>Aram</td></table>
<table><td>Shilan</td></table>
<table><td>Hana <b></td></table>Real output, PHP 8.2 with MariaDB 10.4: one table per member, and the <b> of the third name reaches the browser as a real tag.
ڕاستەقینە، PHP 8.2 لەگەڵ MariaDB 10.4: بۆ هەر ئەندامێک و <b>ـی ناوی سێیەم وەک ڕاستەقینە دەگاتە .
Hintئاماژە
Look at the HTML the loop builds, at what is printed without protection, at what the query reads, and at what happens when config.php is missing.
سەیری ئەو HTMLـە بکە کە دروستی دەکات، ئەوەی بەبێ پاراستن چاپ دەکرێت، ئەوەی دەیخوێنێتەوە و ئەوەی ڕوودەدات ئەگەر config.php نەبێت.
Show answerوەڵامەکە پیشان بدە
- A new
<table>for every row. The loop must print only the rows; open<table>once before it and close it once after it. <td>without<tr>. Every cell belongs inside a table row:<tr><td>…</td></tr>.- No
htmlspecialchars(). A name such asHana <b>becomes real HTML (everything after it turns bold); a script in a name would run: . SELECT *for one column. Ask only for what the page shows:SELECT first_name, and addORDER BYso the order is fixed.includefor a file the page cannot work without. Ifconfig.phpis missing,includeonly warns and the page goes on to fail with mysqli_query(): Argument #1 ($mysql) must be of type mysqli, null given.require_oncestops at once with a clear message (include_once and require_once).- No plan for an empty table, and a misleading name. Print No members yet when there are no rows (
mysqli_num_rows()); and$sqlholds a result, not SQL, so call it$result.
- نوێ بۆ هەر . دەبێت تەنها چاپ بکات؛
<table>یەک جار پێش بکەرەوە و یەک جار دوای ئەو دایبخە. <td>بەبێ<tr>. هەموو خانەیەک دەبێت لەناو بێت:<tr><td>…</td></tr>.htmlspecialchars()نییە. ناوێکی وەکHana <b>دەبێتە HTMLـی ڕاستەقینە (هەموو شتێکی دوای ئەو تۆخ دەبێت)؛ سکریپتێک لەناو ناوێکدا جێبەجێ دەبوو: .SELECT *بۆ یەک . تەنها داوای ئەوە بکە کە لاپەڕەکە پیشانی دەدات:SELECT first_nameوORDER BYزیاد بکە بۆ ئەوەی ڕیزبەندییەکە جێگیر بێت.includeبۆ فایلێک کە لاپەڕەکە بەبێ ئەو کار ناکات. ئەگەرconfig.phpنەبێت،includeتەنها دەدات و لاپەڕەکە بەردەوام دەبێت تا بە mysqli_query(): Argument #1 ($mysql) must be of type mysqli, null given دەشکێت.require_onceیەکسەر بە پەیامێکی ڕوون دەوەستێت (include_once و require_once).- هیچ پلانێک بۆ بەتاڵ نییە و ناوێکی چەواشەکار. کاتێک هیچ نییە No members yet چاپ بکە (
mysqli_num_rows())؛$sqlهەڵدەگرێت نەک SQL، بۆیە ناوی بنێ$result.
<?php
require_once 'includes/config.php'; // stops the page if the file is missing
$result = mysqli_query($db, "SELECT first_name FROM members ORDER BY first_name");
?>
<table>
<tr><th>First name</th></tr>
<?php if (mysqli_num_rows($result) === 0): ?>
<tr><td>No members yet</td></tr>
<?php endif; ?>
<?php while ($row = mysqli_fetch_assoc($result)): ?>
<tr><td><?= htmlspecialchars($row['first_name']) ?></td></tr>
<?php endwhile; ?>
</table>HTML sent to the browserئەو HTMLـەی دەنێردرێت بۆ تۆڕگەڕ
<table>
<tr><th>First name</th></tr>
<tr><td>Aram</td></tr>
<tr><td>Hana <b></td></tr>
<tr><td>Shilan</td></tr>
</table>Real output, PHP 8.2 with MariaDB 10.4: one table, and Hana's <b> is shown as text.
ڕاستەقینە، PHP 8.2 لەگەڵ MariaDB 10.4: یەک و <b>ـی Hana وەک دەق پیشان دەدرێت.
The style belongs in the page's <head> (or a CSS file), and padding: 50px makes huge cells. The student's comment "table and field names are case-sensitive" is only half true: see Common mistakes.
شێوازەکە (style) لە <head>ـی لاپەڕەکەدا (یان فایلێکی CSS) جێی دەبێتەوە و padding: 50px خانەی زۆر گەورە دروست دەکات. خوێندکارەکە «ناوی و » تەنها نیوەی ڕاستە: بڕوانە هەڵە باوەکان.
Summary and reviewپوختە و پێداچوونەوە
- Fixed values:
$con->query($sql)ormysqli_query($con, $sql); check the result withtry/catchin PHP 8.1+. - Visitor values: prepare (
?), bind ("ssi"), execute — or PDOprepare()+execute([...]). - Passwords:
password_hash()to save (VARCHAR 255),password_verify()to check. Nevermd5(),sha1()or plain text. - Reading:
SELECT, thenwhile ($row = mysqli_fetch_assoc($result)); escape output withhtmlspecialchars(). mysqli_fetch_array()modes: BOTH (default), ASSOC, NUM;mysqli_num_rows()/$result->num_rowscounts the result rows.LIMIT ntakes at most n rows,OFFSET mskips m rows; page p =LIMIT k OFFSET (p − 1) × k, always withORDER BY.
- نرخی جێگیر:
$con->query($sql)یانmysqli_query($con, $sql)؛ لە PHP 8.1+دا ئەنجامەکە بەtry/catchبپشکنە. - نرخی سەردانکەر: ئامادەکردن (
?)، بەستنەوە ("ssi")، جێبەجێکردن — یان PDOprepare()+execute([...]). - :
password_hash()بۆ هەڵگرتن (VARCHAR 255)،password_verify()بۆ پشکنین. هەرگیزmd5()،sha1()یان دەقی ئاسایی نا. - خوێندنەوە:
SELECT، پاشانwhile ($row = mysqli_fetch_assoc($result))؛ بەhtmlspecialchars()پاک بکەرەوە. - شێوازەکانی
mysqli_fetch_array(): BOTH (بنەڕەتی)، ASSOC، NUM؛mysqli_num_rows()/$result->num_rowsئەنجام دەژمێرێت. LIMIT nزۆرترین n وەردەگرێت،OFFSET mm تێدەپەڕێنێت؛ لاپەڕەی p =LIMIT k OFFSET (p − 1) × k، هەمیشە لەگەڵORDER BY.
What is a prepared statement and why use it?ڕستەی ئامادەکراو چییە و بۆچی بەکاری دەهێنین؟
An SQL statement sent with parameter markers (? or :name) instead of values; the values are sent separately, so visitor input can never change the SQL. It prevents SQL injection and handles apostrophes correctly.
فەرمانێکی SQLـە کە لە جیاتی نرخەکان بە (? یان :name) دەنێردرێت؛ نرخەکان بە جیا دەنێردرێن، بۆیە نێوخستەی سەردانکەر هەرگیز ناتوانێت SQLـەکە بگۆڕێت. ڕێگە لە دەگرێت و ئەپۆسترۆفیش بە دروستی هەڵدەگیرێت.
What does "fetch" mean, and what does mysqli_fetch_array() return?«fetch» واتای چییە و mysqli_fetch_array() چی دەگەڕێنێتەوە؟
To fetch is to go and bring back data from a source. mysqli_fetch_array() returns the next row of the result set as an array (by default with number and name keys), or null when no rows are left.
fetch واتە چوون و هێنانەوەی زانیاری لە سەرچاوەیەکەوە. mysqli_fetch_array() دواتری وەک دەگەڕێنێتەوە (بە شێوەی بنەڕەتی بە کلیلی ژمارەیی و ناو)، یان null کاتێک نەما.
What is the difference between mysqli_num_rows($result) and $result->num_rows?جیاوازیی نێوان mysqli_num_rows($result) و $result->num_rows چییە؟
None in the result: the first is the procedural function, the second the property of the result object. Both give the number of rows the SELECT returned.
لە ئەنجامدا هیچ: یەکەمیان پرۆسیجەرییە، دووەمیان ئەنجامەکەیە. هەردووکیان ژمارەی ئەو دەدەن کە SELECT گەڕاندوونیەتەوە.
Explain LIMIT and OFFSET with an example.LIMIT و OFFSET بە نموونەیەک ڕوون بکەرەوە.
LIMIT sets the maximum number of rows returned; OFFSET skips rows first. SELECT * FROM Orders ORDER BY id LIMIT 10 OFFSET 15 skips 15 rows and returns rows 16–25. Together they are used for pagination.
LIMIT زۆرترین ژمارەی گەڕاوە دیاری دەکات؛ OFFSET سەرەتا تێدەپەڕێنێت. SELECT * FROM Orders ORDER BY id LIMIT 10 OFFSET 15 پازدە تێدەپەڕێنێت و 16–25 دەگەڕێنێتەوە. پێکەوە بۆ لاپەڕەبەندی بەکار دێن.
List the security problems of Lab 7's original insert code.کێشە ئاسایشییەکانی کۆدی زیادکردنی ئەسڵیی تاقیگەی 7 ڕیز بکە.
SQL injection (values pasted into the SQL), SHA-1 password hashing called "encryption", and mysql_error(), which no longer exists. Fix: prepared statement, password_hash(), try/catch.
(نرخەکان دەخرێنە ناو SQLـەوە)، بە SHA-1 کە ناوی نراوە «» و mysql_error() کە ئیتر بوونی نییە. چارەسەر: ، password_hash()، try/catch.
Videoڤیدیۆ
Watch a videoسەیری ڤیدیۆیەک بکە
A video that viewers liked a lot and praised in their comments, chosen for this lesson.ڤیدیۆیەک کە بینەران زۆریان پێ خۆش بووە و لە کۆمێنتەکانیاندا ستایشیان کردووە، بۆ ئەم وانەیە هەڵبژێردراوە.
See named placeholders, then the four CRUD operations written with PDO prepared statements: fetch one row, insert, update and delete.
سەیری ناودار بکە، پاشان چوار کارەکەی کە بە نووسراون: یەک ، زیادکردن، گۆڕین و سڕینەوە.
From 13:35: queries, prepared statements, row count, INSERT and LIMIT with PDO
لە 13:35 بەدواوە: ، ، ژمارەی ، و بە .
Lesson complete!وانەکە تەواو بوو!
Well done. Review the key terms, then go on to the next lesson.دەستخۆش. سەیرێکی زاراوە سەرەکییەکان بکەرەوە، پاشان بڕۆ بۆ وانەی داهاتوو.
Key termsزاراوە سەرەکییەکان
Securing Data in PHP and MySQLپاراستنی دراوە لە PHP و MySQL
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 ن.