![](https://forums.phpfreaks.com/uploads/set_resources_1/84c1e40ea0e759e3f1505eb1788ddf3c_pattern.png)
ignace
-
Posts
6,457 -
Joined
-
Last visited
-
Days Won
26
Posts posted by ignace
-
-
- It's good practice to assume that if data was entered by a user it is dangerous and should be validated and not be input directly into any strings ("INSERT INTO users (username) VALUE('$username')") or in any HTML (<span><?= $username ?></span>) without validating it thoroughly.
-
What do you mean with constants? Do you mean define('SOME_CONSTANT', 'some value'); or are you referring to the 'AND', 'OR' in my example?
- @param, @return are for documentation, you can learn more about them here: https://docs.phpdoc.org/3.0/guide/references/phpdoc/basic-syntax.html#basic-syntax
-
It's a new feature from PHP 8.0+ where you can define the type of a variable.
- In previous versions you would write function foo($bar) when you declared a function. Which meant a lot of boilerplate code inside your functions, now you can just write function foo(string $bar) and you know it's a string.
-
The general rule of thumb is that if the value will not be changed after it's been set initially in your application, use a constant, otherwise use a variable.
- So your DB hostname, port, username and password are gonna be set at the start of your application, and then nothing should be able to change these values.
The first block loops over all fields and then tests each field against a list of known field names, if a match is found, it executes the case statement.
The second block I define the default return value.
The third block I check if the $where variable is empty, it's the same as if (empty($where))
The fourth block I check if there is a result, if there was no result, there is no point in doing a SELECT count(*) query as the count would be 0, which is the same as the default set previously.
I highly recommend you view this video on youtube. It does a great job of explaining everything in depth.
-
<?php //TEMPLATE. //FULLY WORKING! //COMPLETE! //mysqli_stmt_store_result(). //mysqli_stmt_free_result(). //$rows_count = mysqli_stmt_num_rows($stmt). //mysqli_stmt_get_result(). //http_build_query(). //Report Error. ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); //Valid $_GET Items. //$tables = array('spidered_web_index','$submitted_web_index','$items_listings','visiting_history','following_history'); $spidered_web_index = ['id', 'date_and_time', 'domain', 'url', 'title', 'header', 'meta_keyword', 'meta_description', 'keyword', 'keyphrase']; $submitted_web_index = ['id', 'date_and_time', 'domain', 'url', 'title', 'description', 'country', 'keyword', 'keyphrase']; $items_listings = ['id', 'date_and_time', 'item', 'brand', 'manufacturer', 'model', 'heading', 'year', 'description', 'price', 'country', 'keyword', 'keyphrase']; $visiting_history = ['id', 'date_and_time', 'searcher', 'domain', 'url', 'title', 'header', 'description', 'country', 'meta_keyword', 'meta_description', 'keyword', 'keyphrase']; $following_history = ['id', 'date_and_time', 'searcher', 'follower', 'domain', 'url', 'title', 'header', 'description', 'country', 'meta_keyword', 'meta_description', 'keyword', 'keyphrase']; $tables = [ 'spidered_web_index' => [ 'id' => 'ID', 'date_and_time' => 'Date & Time', 'domain' => 'Domain', 'domain_point' => 'Domain Point', 'url' => 'Url', 'url' => 'Url Point', 'title' => 'Title', 'title' => 'Title Point', 'heading_1' => 'Heading 1', 'heading_1_point' => 'Heading 1 Point', 'heading_2' => 'Heading 2', 'heading_2_point' => 'Heading 2 Point', 'heading_3' => 'Heading 3', 'heading_3_point' => 'Heading 3 Point', 'heading_4' => 'Heading 4', 'heading_4_point' => 'Heading 4 Point', 'heading_5' => 'Heading 5', 'heading_5_point' => 'Heading 5 Point', 'heading_6' => 'Heading 6', 'heading_6_point' => 'Heading 6 Point', 'keyword_superscript' => 'Keyword Superscript', 'keyword_superscript' => 'Keyword superscript', 'keyword_strong' => 'Keyword Strong', 'keyword_strong' => 'Keyword Strong', 'keyword_emphasised' => 'Keyword Emphasised', 'keyword_emphasised' => 'Keyword Emphasised', 'keyword_bold' => 'Keyword Bold', 'keyword_bold' => 'Keyword Bold', 'keyword_italic' => 'Keyword Italic', 'keyword_italic' => 'Keyword Italic', 'keyword_marked' => 'Keyword Marked', 'keyword_marked' => 'Keyword Marked', 'keyword_inserted' => 'Keyword Inserted', 'keyword_inserted' => 'Keyword Inserted', 'keyword_deleted' => 'Keyword Deleted', 'keyword_deleted' => 'Keyword Deleted', 'keyword_small' => 'Keyword Small', 'keyword_small' => 'Keyword Small', 'keyword_subscript' => 'Keyword Subscript', 'keyword_subscript' => 'Keyword Subscript', ], 'submitted_web_index' => [ 'id' => 'ID', 'date_and_time' => 'Date & Time', 'domain' => 'Domain', 'domain_point' => 'Domain Point', 'url' => 'Url', 'url' => 'Url Point', 'title' => 'Title', 'title' => 'Title Point', 'heading_1' => 'Heading 1', 'heading_1_point' => 'Heading 1 Point', 'heading_2' => 'Heading 2', 'heading_2_point' => 'Heading 2 Point', 'heading_3' => 'Heading 3', 'heading_3_point' => 'Heading 3 Point', 'heading_4' => 'Heading 4', 'heading_4_point' => 'Heading 4 Point', 'heading_5' => 'Heading 5', 'heading_5_point' => 'Heading 5 Point', 'heading_6' => 'Heading 6', 'heading_6_point' => 'Heading 6 Point', 'keyword_superscript' => 'Keyword Superscript', 'keyword_superscript' => 'Keyword superscript', 'keyword_strong' => 'Keyword Strong', 'keyword_strong' => 'Keyword Strong', 'keyword_emphasised' => 'Keyword Emphasised', 'keyword_emphasised' => 'Keyword Emphasised', 'keyword_bold' => 'Keyword Bold', 'keyword_bold' => 'Keyword Bold', 'keyword_italic' => 'Keyword Italic', 'keyword_italic' => 'Keyword Italic', 'keyword_marked' => 'Keyword Marked', 'keyword_marked' => 'Keyword Marked', 'keyword_inserted' => 'Keyword Inserted', 'keyword_inserted' => 'Keyword Inserted', 'keyword_deleted' => 'Keyword Deleted', 'keyword_deleted' => 'Keyword Deleted', 'keyword_small' => 'Keyword Small', 'keyword_small' => 'Keyword Small', 'keyword_subscript' => 'Keyword Subscript', 'keyword_subscript' => 'Keyword Subscript', ], 'links_crawls_drummin' => [ 'id' => "ID", 'date_and_time' => "Date and Time", 'domain' => "Domain", 'domain_point' => "Domain Point", 'url' => "URL", 'url_point' => "Url Point", 'title' => "Title", 'title_point' => "Title Point", 'header' => "Header", 'header_point' => "Header Point", 'kw_1' => "Keyword 1", 'kw_1_point' => "Keyword 1 Point", 'kw_2' => "Keyword 2", 'kw_2_point' => "Keyword 2 Point", 'kw_3' => "Keyword 3", 'kw_3_point' => "Keyword 3 Point", 'kw_4' => "Keyword 4", 'kw_4_point' => "Keyword 4 Point", 'description' => "Description", 'description_point' => "Description Point", ], 'links_submits_drummin' => [ 'id' => "ID", 'date_and_time' => "Date and Time", 'domain' => "Domain", 'domain_point' => "Domain Point", 'url' => "URL", 'url_point' => "Url Point", 'title' => "Title", 'title_point' => "Title Point", 'header' => "Header", 'header_point' => "Header Point", 'kw_1' => "Keyword 1", 'kw_1_point' => "Keyword 1 Point", 'kw_2' => "Keyword 2", 'kw_2_point' => "Keyword 2 Point", 'kw_3' => "Keyword 3", 'kw_3_point' => "Keyword 3 Point", 'kw_4' => "Keyword 4", 'kw_4_point' => "Keyword 4 Point", 'description' => "Description", 'description_point' => "Description Point", ], ]; //Extract $_GETs. $table = !empty($_POST['tbl']) ? $_POST['tbl'] : (!empty($_GET['tbl']) ? $_GET['tbl'] : 'spidered_web_index'); $column = !empty($_POST['col']) ? $_POST['col'] : (!empty($_GET['col']) ? $_GET['col'] : 'id'); $limit = !empty($_POST['lmt']) ? $_POST['lmt'] : (!empty($_GET['lmt']) ? $_GET['lmt'] : 1); $match = !empty($_POST['mtch']) ? $_POST['mtch'] : (!empty($_GET['mtch']) ? $_GET['mtch'] : 'fuzzy'); $search = !empty($_POST['srch']) ? $_POST['srch'] : (!empty($_GET['srch']) ? $_GET['srch'] : 'mobile'); $page = !empty($_GET['pg']) ? intval($_GET['pg']) : 1; $offset = $page * $limit - $limit; $headings = array_values($tables[$table]); $columns = array_keys($tables[$table]); echo $columns_count = count($columns); if (!in_array($column, $columns)) { die('Invalid Column!'); } if (!in_array($tables[$table], $tables)) { die('Invalid Table!'); } $search = !empty($search) && $match === 'fuzzy' ? str_replace('*', '%', $search) : $search; $char_types = str_repeat('s', $columns_count); $search_values = array_fill(0, $columns_count, $search); $comparator = $match === 'exact' ? '=' : 'LIKE'; $sql = "SELECT * FROM $table WHERE " . implode(" $comparator ? OR ", $columns) . " $comparator ? "; $sql .= "ORDER BY id DESC LIMIT $limit OFFSET $offset"; //Query DB. mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); $conn = mysqli_connect("localhost", "root", "", "buzz"); //mysqli_connect("server","user","password","db"); mysqli_set_charset($conn, 'utf8mb4'); if (mysqli_connect_errno()) { printf("Mysqli Connection Error: %s", mysqli_connect_error()); } $stmt = mysqli_stmt_init($conn); if (!mysqli_stmt_prepare($stmt, $sql)) { //Fetch Rows based on Row Limit per page. echo 'Mysqli Error: ' . mysqli_stmt_error($stmt); echo '<br>'; echo 'Mysqli Error No: ' . mysqli_stmt_errno($stmt); } else { mysqli_stmt_bind_param($stmt, $char_types, ...$search_values); mysqli_stmt_execute($stmt); mysqli_stmt_store_result($stmt); //Necessary to use with mysqli_stmt_num_rows() when SQL query is SELECT. echo '<br><br>'; echo '<br><br>'; echo '<br><br>'; echo '<br><br>'; //Fetch Matching Rows Count. //mysqli_stmt_num_rows() has to come after mysqli_stmt_store_result(). echo 'Total Result: ' . ($rows_count = mysqli_stmt_num_rows($stmt)); echo '<br><br>'; mysqli_stmt_free_result($stmt); //Is this really necessary ? if (!mysqli_stmt_prepare($stmt, $sql)) { //Fetch Rows based on Row Limit per page. echo 'Mysqli Error: ' . mysqli_stmt_error($stmt); echo '<br>'; echo 'Mysqli Error No: ' . mysqli_stmt_errno($stmt); } else { mysqli_stmt_bind_param($stmt, $char_types, ...$search_values); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); mysqli_stmt_free_result($stmt); //Is this really necessary ? while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { for ($i = 0; $i < $columns_count; $i++) { echo '<b>' . $headings[$i] . ': </b>' . $row[$columns[$i]]; echo '<br>'; } } } } mysqli_stmt_close($stmt); mysqli_close($conn); echo 'Total Pages: ' . ($total_pages = ceil($rows_count / $limit)); echo '<br><br>'; $i = 0; while ($i < $total_pages) { $i++; $pagination_section_array = ["tbl" => "$table", "mtch" => "$match", "lmt" => "$limit", "srch" => "$search", "pg" => intval($i)]; $serps_url = $_SERVER['PHP_SELF'] . '?' . http_build_query($pagination_section_array, '', '&'); //Did convert '&' to '&' and so NO need to add htmlspecialchars() when echoing link. if ($i == $page) { echo '<a href="' . $serps_url . '">' . "<b>$i</b>" . '</a>'; //No need to add htmlspecialchars(), to convert '&' to '&', when echoing link here. } else { echo '<a href="' . $serps_url . '">' . "$i" . '</a>'; //No need to add htmlspecialchars(), to convert '&' to '&', when echoing link here. } } echo '<br>'; ?>
To improve this script, the first thing I would do is cut out the large arrays. So I would create a new directory called config and create a new file tables.php to hold the table configuration, like this:
search-engine-project/ ├─ config/ │ ├─ tables.php
Then in tables.php I would paste the large array:
<?php return [ 'spidered_web_index' => [ 'id' => 'ID', 'date_and_time' => 'Date & Time', 'domain' => 'Domain', 'domain_point' => 'Domain Point', 'url' => 'Url', 'url' => 'Url Point', 'title' => 'Title', 'title' => 'Title Point', 'heading_1' => 'Heading 1', 'heading_1_point' => 'Heading 1 Point', 'heading_2' => 'Heading 2', 'heading_2_point' => 'Heading 2 Point', 'heading_3' => 'Heading 3', 'heading_3_point' => 'Heading 3 Point', 'heading_4' => 'Heading 4', 'heading_4_point' => 'Heading 4 Point', 'heading_5' => 'Heading 5', 'heading_5_point' => 'Heading 5 Point', 'heading_6' => 'Heading 6', 'heading_6_point' => 'Heading 6 Point', 'keyword_superscript' => 'Keyword Superscript', 'keyword_superscript' => 'Keyword superscript', 'keyword_strong' => 'Keyword Strong', 'keyword_strong' => 'Keyword Strong', 'keyword_emphasised' => 'Keyword Emphasised', 'keyword_emphasised' => 'Keyword Emphasised', 'keyword_bold' => 'Keyword Bold', 'keyword_bold' => 'Keyword Bold', 'keyword_italic' => 'Keyword Italic', 'keyword_italic' => 'Keyword Italic', 'keyword_marked' => 'Keyword Marked', 'keyword_marked' => 'Keyword Marked', 'keyword_inserted' => 'Keyword Inserted', 'keyword_inserted' => 'Keyword Inserted', 'keyword_deleted' => 'Keyword Deleted', 'keyword_deleted' => 'Keyword Deleted', 'keyword_small' => 'Keyword Small', 'keyword_small' => 'Keyword Small', 'keyword_subscript' => 'Keyword Subscript', 'keyword_subscript' => 'Keyword Subscript', ], 'submitted_web_index' => [ 'id' => 'ID', 'date_and_time' => 'Date & Time', 'domain' => 'Domain', 'domain_point' => 'Domain Point', 'url' => 'Url', 'url' => 'Url Point', 'title' => 'Title', 'title' => 'Title Point', 'heading_1' => 'Heading 1', 'heading_1_point' => 'Heading 1 Point', 'heading_2' => 'Heading 2', 'heading_2_point' => 'Heading 2 Point', 'heading_3' => 'Heading 3', 'heading_3_point' => 'Heading 3 Point', 'heading_4' => 'Heading 4', 'heading_4_point' => 'Heading 4 Point', 'heading_5' => 'Heading 5', 'heading_5_point' => 'Heading 5 Point', 'heading_6' => 'Heading 6', 'heading_6_point' => 'Heading 6 Point', 'keyword_superscript' => 'Keyword Superscript', 'keyword_superscript' => 'Keyword superscript', 'keyword_strong' => 'Keyword Strong', 'keyword_strong' => 'Keyword Strong', 'keyword_emphasised' => 'Keyword Emphasised', 'keyword_emphasised' => 'Keyword Emphasised', 'keyword_bold' => 'Keyword Bold', 'keyword_bold' => 'Keyword Bold', 'keyword_italic' => 'Keyword Italic', 'keyword_italic' => 'Keyword Italic', 'keyword_marked' => 'Keyword Marked', 'keyword_marked' => 'Keyword Marked', 'keyword_inserted' => 'Keyword Inserted', 'keyword_inserted' => 'Keyword Inserted', 'keyword_deleted' => 'Keyword Deleted', 'keyword_deleted' => 'Keyword Deleted', 'keyword_small' => 'Keyword Small', 'keyword_small' => 'Keyword Small', 'keyword_subscript' => 'Keyword Subscript', 'keyword_subscript' => 'Keyword Subscript', ], 'links_crawls_drummin' => [ 'id' => "ID", 'date_and_time' => "Date and Time", 'domain' => "Domain", 'domain_point' => "Domain Point", 'url' => "URL", 'url_point' => "Url Point", 'title' => "Title", 'title_point' => "Title Point", 'header' => "Header", 'header_point' => "Header Point", 'kw_1' => "Keyword 1", 'kw_1_point' => "Keyword 1 Point", 'kw_2' => "Keyword 2", 'kw_2_point' => "Keyword 2 Point", 'kw_3' => "Keyword 3", 'kw_3_point' => "Keyword 3 Point", 'kw_4' => "Keyword 4", 'kw_4_point' => "Keyword 4 Point", 'description' => "Description", 'description_point' => "Description Point", ], 'links_submits_drummin' => [ 'id' => "ID", 'date_and_time' => "Date and Time", 'domain' => "Domain", 'domain_point' => "Domain Point", 'url' => "URL", 'url_point' => "Url Point", 'title' => "Title", 'title_point' => "Title Point", 'header' => "Header", 'header_point' => "Header Point", 'kw_1' => "Keyword 1", 'kw_1_point' => "Keyword 1 Point", 'kw_2' => "Keyword 2", 'kw_2_point' => "Keyword 2 Point", 'kw_3' => "Keyword 3", 'kw_3_point' => "Keyword 3 Point", 'kw_4' => "Keyword 4", 'kw_4_point' => "Keyword 4 Point", 'description' => "Description", 'description_point' => "Description Point", ], ];
Then back to our main script I would then include the configuration like this:
<?php ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); $items_listings = ['id', 'date_and_time', 'item', 'brand', 'manufacturer', 'model', 'heading', 'year', 'description', 'price', 'country', 'keyword', 'keyphrase']; $visiting_history = ['id', 'date_and_time', 'searcher', 'domain', 'url', 'title', 'header', 'description', 'country', 'meta_keyword', 'meta_description', 'keyword', 'keyphrase']; $following_history = ['id', 'date_and_time', 'searcher', 'follower', 'domain', 'url', 'title', 'header', 'description', 'country', 'meta_keyword', 'meta_description', 'keyword', 'keyphrase']; // refactored part $tables = require 'config/tables.php'; $spidered_web_index = array_keys($tables['spidered_web_index']); $submitted_web_index = array_keys($tables['submitted_web_index']); // end refactored part //Extract $_GETs. $table = !empty($_POST['tbl']) ? $_POST['tbl'] : (!empty($_GET['tbl']) ? $_GET['tbl'] : 'spidered_web_index'); $column = !empty($_POST['col']) ? $_POST['col'] : (!empty($_GET['col']) ? $_GET['col'] : 'id'); $limit = !empty($_POST['lmt']) ? $_POST['lmt'] : (!empty($_GET['lmt']) ? $_GET['lmt'] : 1); $match = !empty($_POST['mtch']) ? $_POST['mtch'] : (!empty($_GET['mtch']) ? $_GET['mtch'] : 'fuzzy'); $search = !empty($_POST['srch']) ? $_POST['srch'] : (!empty($_GET['srch']) ? $_GET['srch'] : 'mobile'); $page = !empty($_GET['pg']) ? intval($_GET['pg']) : 1; $offset = $page * $limit - $limit; $headings = array_values($tables[$table]); $columns = array_keys($tables[$table]); echo $columns_count = count($columns); if (!in_array($column, $columns)) { die('Invalid Column!'); } if (!in_array($tables[$table], $tables)) { die('Invalid Table!'); } $search = !empty($search) && $match === 'fuzzy' ? str_replace('*', '%', $search) : $search; $char_types = str_repeat('s', $columns_count); $search_values = array_fill(0, $columns_count, $search); $comparator = $match === 'exact' ? '=' : 'LIKE'; $sql = "SELECT * FROM $table WHERE " . implode(" $comparator ? OR ", $columns) . " $comparator ? "; $sql .= "ORDER BY id DESC LIMIT $limit OFFSET $offset"; //Query DB. mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); $conn = mysqli_connect("localhost", "root", "", "buzz"); //mysqli_connect("server","user","password","db"); mysqli_set_charset($conn, 'utf8mb4'); if (mysqli_connect_errno()) { printf("Mysqli Connection Error: %s", mysqli_connect_error()); } $stmt = mysqli_stmt_init($conn); if (!mysqli_stmt_prepare($stmt, $sql)) { //Fetch Rows based on Row Limit per page. echo 'Mysqli Error: ' . mysqli_stmt_error($stmt); echo '<br>'; echo 'Mysqli Error No: ' . mysqli_stmt_errno($stmt); } else { mysqli_stmt_bind_param($stmt, $char_types, ...$search_values); mysqli_stmt_execute($stmt); mysqli_stmt_store_result($stmt); //Necessary to use with mysqli_stmt_num_rows() when SQL query is SELECT. echo '<br><br>'; echo '<br><br>'; echo '<br><br>'; echo '<br><br>'; //Fetch Matching Rows Count. //mysqli_stmt_num_rows() has to come after mysqli_stmt_store_result(). echo 'Total Result: ' . ($rows_count = mysqli_stmt_num_rows($stmt)); echo '<br><br>'; mysqli_stmt_free_result($stmt); //Is this really necessary ? if (!mysqli_stmt_prepare($stmt, $sql)) { //Fetch Rows based on Row Limit per page. echo 'Mysqli Error: ' . mysqli_stmt_error($stmt); echo '<br>'; echo 'Mysqli Error No: ' . mysqli_stmt_errno($stmt); } else { mysqli_stmt_bind_param($stmt, $char_types, ...$search_values); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); mysqli_stmt_free_result($stmt); //Is this really necessary ? while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { for ($i = 0; $i < $columns_count; $i++) { echo '<b>' . $headings[$i] . ': </b>' . $row[$columns[$i]]; echo '<br>'; } } } } mysqli_stmt_close($stmt); mysqli_close($conn); echo 'Total Pages: ' . ($total_pages = ceil($rows_count / $limit)); echo '<br><br>'; $i = 0; while ($i < $total_pages) { $i++; $pagination_section_array = ["tbl" => "$table", "mtch" => "$match", "lmt" => "$limit", "srch" => "$search", "pg" => intval($i)]; $serps_url = $_SERVER['PHP_SELF'] . '?' . http_build_query($pagination_section_array, '', '&'); //Did convert '&' to '&' and so NO need to add htmlspecialchars() when echoing link. if ($i == $page) { echo '<a href="' . $serps_url . '">' . "<b>$i</b>" . '</a>'; //No need to add htmlspecialchars(), to convert '&' to '&', when echoing link here. } else { echo '<a href="' . $serps_url . '">' . "$i" . '</a>'; //No need to add htmlspecialchars(), to convert '&' to '&', when echoing link here. } } echo '<br>'; ?>
Then as the second step I would cut out the pagination HTML into a separate directory (html) and file (pagination.php), like this:
search-engine-project/ ├─ config/ │ ├─ tables.php ├─ html/ │ ├─ pagination.php
In our pagination.php file:
<?php for ($i = 0; $i < $total_pages; $i++): ?> <a href="?<?= http_build_query(array_merge($pagination_section_array, ['pg' => $i]), '', '&') ?>"> <?php if ($i == $page): ?> <b> <?php endif ?> <?= $i ?> <?php if ($i == $page): ?> </b> <?php endif ?> </a> <?php endfor ?> <br>
Then back to our original script (scroll to the end):
<?php ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); $items_listings = ['id', 'date_and_time', 'item', 'brand', 'manufacturer', 'model', 'heading', 'year', 'description', 'price', 'country', 'keyword', 'keyphrase']; $visiting_history = ['id', 'date_and_time', 'searcher', 'domain', 'url', 'title', 'header', 'description', 'country', 'meta_keyword', 'meta_description', 'keyword', 'keyphrase']; $following_history = ['id', 'date_and_time', 'searcher', 'follower', 'domain', 'url', 'title', 'header', 'description', 'country', 'meta_keyword', 'meta_description', 'keyword', 'keyphrase']; $tables = require 'config/tables.php'; $spidered_web_index = array_keys($tables['spidered_web_index']); $submitted_web_index = array_keys($tables['submitted_web_index']); //Extract $_GETs. $table = !empty($_POST['tbl']) ? $_POST['tbl'] : (!empty($_GET['tbl']) ? $_GET['tbl'] : 'spidered_web_index'); $column = !empty($_POST['col']) ? $_POST['col'] : (!empty($_GET['col']) ? $_GET['col'] : 'id'); $limit = !empty($_POST['lmt']) ? $_POST['lmt'] : (!empty($_GET['lmt']) ? $_GET['lmt'] : 1); $match = !empty($_POST['mtch']) ? $_POST['mtch'] : (!empty($_GET['mtch']) ? $_GET['mtch'] : 'fuzzy'); $search = !empty($_POST['srch']) ? $_POST['srch'] : (!empty($_GET['srch']) ? $_GET['srch'] : 'mobile'); $page = !empty($_GET['pg']) ? intval($_GET['pg']) : 1; $offset = $page * $limit - $limit; $headings = array_values($tables[$table]); $columns = array_keys($tables[$table]); echo $columns_count = count($columns); if (!in_array($column, $columns)) { die('Invalid Column!'); } if (!in_array($tables[$table], $tables)) { die('Invalid Table!'); } $search = !empty($search) && $match === 'fuzzy' ? str_replace('*', '%', $search) : $search; $char_types = str_repeat('s', $columns_count); $search_values = array_fill(0, $columns_count, $search); $comparator = $match === 'exact' ? '=' : 'LIKE'; $sql = "SELECT * FROM $table WHERE " . implode(" $comparator ? OR ", $columns) . " $comparator ? "; $sql .= "ORDER BY id DESC LIMIT $limit OFFSET $offset"; //Query DB. mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); $conn = mysqli_connect("localhost", "root", "", "buzz"); //mysqli_connect("server","user","password","db"); mysqli_set_charset($conn, 'utf8mb4'); if (mysqli_connect_errno()) { printf("Mysqli Connection Error: %s", mysqli_connect_error()); } $stmt = mysqli_stmt_init($conn); if (!mysqli_stmt_prepare($stmt, $sql)) { //Fetch Rows based on Row Limit per page. echo 'Mysqli Error: ' . mysqli_stmt_error($stmt); echo '<br>'; echo 'Mysqli Error No: ' . mysqli_stmt_errno($stmt); } else { mysqli_stmt_bind_param($stmt, $char_types, ...$search_values); mysqli_stmt_execute($stmt); mysqli_stmt_store_result($stmt); //Necessary to use with mysqli_stmt_num_rows() when SQL query is SELECT. echo '<br><br>'; echo '<br><br>'; echo '<br><br>'; echo '<br><br>'; //Fetch Matching Rows Count. //mysqli_stmt_num_rows() has to come after mysqli_stmt_store_result(). echo 'Total Result: ' . ($rows_count = mysqli_stmt_num_rows($stmt)); echo '<br><br>'; mysqli_stmt_free_result($stmt); //Is this really necessary ? if (!mysqli_stmt_prepare($stmt, $sql)) { //Fetch Rows based on Row Limit per page. echo 'Mysqli Error: ' . mysqli_stmt_error($stmt); echo '<br>'; echo 'Mysqli Error No: ' . mysqli_stmt_errno($stmt); } else { mysqli_stmt_bind_param($stmt, $char_types, ...$search_values); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); mysqli_stmt_free_result($stmt); //Is this really necessary ? while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) { for ($i = 0; $i < $columns_count; $i++) { echo '<b>' . $headings[$i] . ': </b>' . $row[$columns[$i]]; echo '<br>'; } } } } mysqli_stmt_close($stmt); mysqli_close($conn); echo 'Total Pages: ' . ($total_pages = ceil($rows_count / $limit)); echo '<br><br>'; $pagination_section_array = ["tbl" => "$table", "mtch" => "$match", "lmt" => "$limit", "srch" => "$search"]; // refactored part require 'html/pagination.php'; // end refactored part ?>
This way bit by bit, the code gets smaller, and easier to maintain.
-
1
-
-
Quote
Q1. Is it true that, I need to use mysqli_stmt_store_result($stmt) prior to using mysqli_stmt_num_rows($stmt) ?
Q2. Is it true that, I need to use mysqli_stmt_free_result($stmt) after every mysqli_stmt_store_result($stmt) ?
Q3. Anything else I need to know apart from I should use pdo ?
Q4. Is my code bad, ok, good or great ? I reckon it is ok.
Q1. yes, it needs a stored result to count the rows.
Q2. yes, it clears the stored result from the internal buffer freeing memory. It's good practice to always clean up after yourself.
Q3. either use mysqli_* or PDO, most people use PDO for its simple API. PDO has no pdo_* functions, only it's Object Oriented interface.
Q4. bad, but not in the way of ugly or "newbie" but because
- it can be exploited by hackers;
- it will be hard to maintain (lots of duplicate code, weird indents, no separation between HTML and PHP, ..);
- using die() in a script is not good UX, the user can't get back to the homepage or try another search;
-
1
-
You avoid SQL injection when you use prepared statements assuming you write the full SQL and don't inject foreign SQL using variables.
So while this is safe:
mysqli_stmt_prepare($stmt, 'SELECT * FROM users WHERE username = ?');
This is not:
mysqli_stmt_prepare($stmt, "SELECT * FROM $tbl WHERE $col_1 = ?");
Because now a hacker can control what the prepared statement is. So this could end up being:
mysqli_stmt_prepare($stmt, "SELECT * FROM users; DELETE FROM users WHERE 1 = ?");
Which is not what you intended.
--
I assume nobody told you, because I don't think anyone read over your code like I did. Walls of text do that to people. Which is why, as a programmer, we divide the program into little pieces like lego blocks. Building or re-using blocks to build a car or a house.
We even have "rules" defining how small things should be: https://refactoring.guru/refactoring/smells/bloaters
--
I assumed it was either a search engine or an SEO kinda tool. You might want to read up on cursor based pagination: https://medium.com/swlh/how-to-implement-cursor-pagination-like-a-pro-513140b65f32 as LIMIT OFFSET is not ideal for pagination (performance wise).
-
1
-
-
Also don't do this:
SELECT id,domain,word,phrase from $tbl WHERE $col_1 = ?
Because now it's easy for a hacker to do something like:
?col_1=1;DELETE FROM users WHERE id&input_1=1
Instead do something like:
/** * @param array{col_1: string} $fields * @param string $operator Possible values AND, OR * @param int $limit Possible value between 0 and 100, if invalid defaults to 100 * @param int $offset * * @return array{items: array, total_items: int} */ function page_search(array $fields, string $operator, int $limit, int $offset = 0): array { $where = $values = []; $operator = in_array($operator, ['OR', 'AND']) ? $operator : 'AND'; $limit = 0 < $limit && $limit < 100 ? $limit : 100; $offset = 0 <= $offset ? $offset : 0; foreach ($fields as $field => $term) { switch ($field) { case 'col_1': $where[] = 'col_1 = ?'; $values[] = $term; break; // other fields you want to allow to search on } $result = [ 'items' => [], 'total_items' => 0, ]; if ([] === $where) { return $result; } $result['items'] = db_fetch_all('SELECT * FROM some_table WHERE ' . implode($operator, $where) . " LIMIT $offset, $limit", $values); if (count($result['items'])) { // only execute a count() query if we have a result $result['total_items'] = db_fetch_column('SELECT count(*) FROM some_table WHERE ' . implode($operator, $where), $values); } return $result; }
By dividing your program into little pieces you reduce the cognitive load necessary to work on pieces of your application or to find and fix bugs.
The same goes for the database. I see you use mysqli_connect in the script with the values hardcoded. Instead you should create a file that holds your configuration values:
// bootstrap.php define('DB_HOST', 'localhost'); define('DB_USER', 'the_username'); define('DB_PASS', 'the_password'); define('DB_NAME', 'the_database'); define('DB_PORT', 3306); define('DB_CHARSET', 'utf8mb4'); define('EMERGENCY_EMAIL', 'some@email.com'); // in case of fatal errors // other configuration values
Then in your database functions file:
function db_connect() { static $connection; if (null === $connection) { $connection = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT); if ($connection) { mysqli_set_charset($connection, DB_CHARSET); } } return $connection; }
All of this creates building blocks you can use to build upon further. The next step would be to create a db_fetch_all function that uses db_connect to get the active DB connection etc...
-
1
-
-
3 minutes ago, TheStudent2023 said:
Sorry. Did not really understand the structure or the code that much. This is adv stuff. Right ? Not intermediate, let alone beginner level. Yes ?
Actually this is beginner level. You start with a directory structure something like:
/functions /html index.php search.php register.php bootstrap.php
Where index.php, search.php, register.php are the files users will visit like http://website.com/search.php and http://website.com/register.php
Inside search.php and register.php the code will look something like:
// defines constants like DB_USER, DB_PASS, DB_HOST, .. so you only need to change this in one place require __DIR__ . '/bootstrap.php'; // load functions you need require APP_PROJECT_DIR . '/functions/page_functions.php'; require APP_PROJECT_DIR . '/functions/search_functions.php'; // do the work // load the HTML require APP_PROJECT_DIR . '/html/search_view.php';
Using this way of working you avoid having a big wall of text and give each file their own responsibility.
-
1
-
-
As for your question on how the pros do it, this is how procedural programmers structure their project:
- load any required files
- get the inputs and validate them and take appropriate action if they are invalid
- do something with the input
- report back to the user
A popular pattern found online is Controller-Model-View. Where your controller is your php file (index.php, search.php, register.php), and the view is your HTML. Your model are the necessary functions you execute to do the thing.
Take for example a search:
Your controller:
// search.php // // Some description // // Arguments: // q: the search query // page: the page number, defaults to 1 // Supports: // GET, HEAD // Usage: // search.php?q=some+query&page=1 require __DIR__ . '/bootstrap.php'; require APP_PROJECT_DIR . '/lib/search_functions.php'; require APP_PROJECT_DIR . '/lib/page_functions.php'; $term = get_search_term(); if (empty($term)) { redirect('/to/the/search/form'); } // more validation here $page = get_page(); $search_results = do_page_search($term, $page, 30 /* results per page */); // $search_results contains: // [items] // [total_items] // [total_pages] // [items_per_page] // [current_page] // everything is rendered in a view require APP_PROJECT_DIR . '/views/search_results.php';
Then the result is passed off to the view:
<?php // views/search_results.php // // Description // require APP_PROJECT_DIR . '/views/header.php'; ?> <div class="search-results"> <?php foreach ($search_results as $search_result): ?> ... <?php endforeach ?> </div> <div class="pagination"> <?php // uses total_items, total_pages, current_page, items_per_page because these are abstract names and can be reused require APP_PROJECT_DIR . '/views/pagination.php'; ?> </div> <?php require APP_PROJECT_DIR . '/views/footer.php'; ?>
-
Hi,
It won't take you 6 years to understand OOP. OOP is about ownership and responsibility. Procedural is about functionality.
From a procedural point of view a user is an array with certain fields like id, username, password, ... Any function can freely use, modify, or even remove these fields as they see fit.
Nobody owns the data, and so anything can do to it what it wants.
In OOP you assign a class with the ownership and responsibility of this data:
class User { private ?int $id = null; private string $username; private string $password; }
In the above example the User class owns the data, and doesn't allow anyone to touch it. Not very useful.
class User { private ?int $id = null; private string $username; private string $password; public function changeUsername(string $username) { assertLength($username, 8); assertNotProfane($username); assertNotSameAsFirstOrLastName($username); assertNotSameAsEmail($username); $this->username = $username; } }
Now the User class allows changing the username, if ALL requirements are met. Otherwise it will throw an Exception.
Exceptions are a big part of OOP, it ensures your code follows a controlled path, like an assembly line, any bad items are cast to a different line.
The advantage of encapsulating data is that there is only one place that controls access to the data, and not hundreds of places.
So that if your requirements change, you can make the change in one place and not spend weeks tracking down all uses.
I am not saying you should switch to OOP, just that it won't take you 6 years as the concept is quite simple.
-
1
-
-
Thanks for the reply, but by now I'm already using that.
I've changed my code so I want to edit my original post, now I'm looking for the edit button... How silly is that
I now have selectbox but it replaced my exisiting textfield, I think I know why that is, textfield holds the value for Game.name but the newly added selectbox holds the value of type.name.
GameType.php
public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add( 'name', EntityType::class, [ 'class' => 'AppBundle:Game', 'choice_label' => 'name', 'label' => 'name' ] ); $builder ->add( 'name', EntityType::class, [ 'class' => 'AppBundle:Type', 'choice_label' => 'name', 'multiple' => false, 'expanded' => false ] ); }
Could it be that it has something to do with the same property, name?Yes. Your field name should explain it's value:
public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add( 'game', EntityType::class, [ 'class' => 'AppBundle:Game', 'choice_label' => 'name', 'label' => 'name' ] ) ->add( 'type', EntityType::class, [ 'class' => 'AppBundle:Type', 'choice_label' => 'name', 'multiple' => false, 'expanded' => false ] ); }
Afterwards you can access these fields like:
$form->get('game') // and $form->get('type')
-
1
-
-
Use the EntityType:
https://symfony.com/doc/current/reference/forms/types/entity.html
-
Shots fired!
-
You can create an interface for every class in the hierarchy, but that's not really useful.
An interface is a type hint essentially. So when you create specialised interfaces there must be a type hint for it in your code to be useful.
-
1
-
-
Hi,
I'm writing a script that scans images for numberplate edges in different angles. The below script works and picks up the edges in ~2 seconds. I'm curious if I can improve this to find the edges even faster.
real 0m1.917s
user 0m1.767s
sys 0m0.125s<?php require __DIR__ . '/../vendor/autoload.php'; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\PointInterface; use Imagine\Image\ImageInterface; use Imagine\Image\Point; $topLeft = $bottomLeft = [99999, 0]; $topRight = $bottomRight = [0, 99999]; $gatherer = function (ColorInterface $color, PointInterface $point, ImageInterface $image) use (&$topLeft, &$topRight, &$bottomLeft, &$bottomRight) { if ($color->getAlpha() == 0) { return; } $x = $point->getX(); $y = $point->getY(); $top = $image->getColorAt(new Point($x, $y - 1))->isOpaque(); $bottom = $image->getColorAt(new Point($x, $y + 1))->isOpaque(); $left = $image->getColorAt(new Point($x - 1, $y))->isOpaque(); $right = $image->getColorAt(new Point($x + 1, $y))->isOpaque(); if (!$top && !$left && $bottom && $right && $x < $topLeft[0]) { $topLeft = [$x, $y]; } if (!$top && !$right && $bottom && $left && $x > $topRight[0]) { $topRight = [$x, $y]; } if (!$bottom && !$left && $top && $right && $x < $bottomLeft[0]) { $bottomLeft = [$x, $y]; } if (!$bottom && !$right && $top && $left && $x > $bottomRight[0]) { $bottomRight = [$x, $y]; } }; //$img = (new \Imagine\Gd\Imagine())->open(__DIR__ . '/numberplate-test.png'); $img = (new \Imagine\Gd\Imagine())->open(__DIR__ . '/numberplate-test2.png'); for ($top = 640; $top < 900; $top++) { for ($left = 340; $left < 1580; $left++) { $point = new Point($left, $top); $gatherer($img->getColorAt($point), $point, $img); } } var_dump($topLeft, $topRight, $bottomLeft, $bottomRight);
-
It's missing a dollar sign. Use an IDE to point out the obvious errors.
-
Ghehe I've been doing this for a long time now. Turning everything into an errorexception. Something along the lines of:
set_error_handler(function($errno, $errstr, ..) { if ($errno === E_USER_DEPRECATED || $errno === E_DEPRECATED) { $this->notifier->sendDeprecatedMail($this->getApplicationInfo(), $errstr, ['file' => $errfile, 'line' => $errline, 'context' => $context]); return false; } if (!(error_reporting() & $errno)) { return false; } throw new ErrorException($errno, $errstr, ..); });
Didnt' knew about the password_hash() thing. Might need to revise
-
$z_offset is undefined. Please turn on error_reporting like so:
error_reporting(-1); ini_set('display_errors', 1);
Also what is your date.timezone setting?
echo date_default_timezone_get();
-
Right off the bat, something like:
for file in $(ls -A); do mv $file 'r'$file; done;
-
No. That is a general class to send e-mails. I would create a specialised interface to send user related e-mails as demonstrated in my example.
This way you can force the required parameters and decouple your code from knowing how to send these sort of e-mails otherwise you'd end up with code like:
$to = $user->toEmail(); $subject = $this->translator->translate('Hello %name%', ['%name%' => $user->getFullName()]); $variables = [ // .. ]; $templateBody = $this->templateManager->getTemplateByName('user_registration'); $this->mailSubmissionAgent->send($to, $subject, $templateBody->resolve($variables), 'noreply@mydomain.com');
While this otherwise would be:
$this->userMailer->sendWelcomeMail($user);
-
public function register($to, $username, $password) { $raw_token = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM); $encoded_token = bin2hex($raw_token); $token_hash = hash('sha256', $raw_token); $hashed_password = password_hash($password, PASSWORD_DEFAULT); try { $sql = ' INSERT INTO users (email, username, password, confirmation_key) VALUES (?, ?, ?, ?)'; $stmt = $this->database->prepare($sql); $stmt->execute([$to, $username, $hashed_password, $token_hash]); $subject = 'Confirm Email'; $body = "Click to activate account\r\n" . APPLICATION_URL . "/activate.php?k=$encoded_token"; $this->mailSubmissionAgent->send($to, $subject, $body, ADMIN_EMAIL_FROM); die(header("Location: ./login.php?confirm")); } catch(PDOException $e) { if ($e->getCode() == '23000') { $error[] = "Registration Failed"; $error[] = "Invalid Username or Email"; show_form_errors($error); } else { throw new Exception($e); } } // End Catch }
You will probably have to create or update users in multiple places in your application so I would introduce a (interface) UserManager (=concrete PDOUserManager) to CRUD users.
I would also create a separate class to send the registration e-mail perhaps one class to send all sort of user related e-mails.
interface UserMailer extends MailSubmissionAgent { public function sendWelcomeMail(User $user); public function sendForgotPasswordMail($email); // you get the idea }
-
Exactly. I first named it UserRegistration and then thought about what you had said before about the interface not being a specific implementation. Such as, this could be a user reg, a customer reg, a vendor reg, a company reg...etc which would be handled by the classes that implement it.
e.g
class UserReg implements Registration
class CustomerReg implements Registration
class VendorReg implements Registration
class CompanyReg implements Registration
Why is the thinking different in this case?
Every registration is a user registration even when it's for a company. Or do your users register their company details but then don't bother creating a username and password?
-
Here are 2 example interfaces.
interface UserRegistration { public function __construct(UserManager $userManager, EventDispatcher $eventDispatcher); public function register(array $data): User; public function getErrors(): array; } interface UserManager { public function findByEmail($email): User; public function createUser(): User; public function updateUser(User $user): UserManager; public function deleteUser(User $user): UserManager; }
The UserRegistration class would receive the data from the client, validate it, and throw an exception if it fails. The encountered errors can be retrieved with getErrors().
If the validation is successful, it creates the user through the UserManager, and insert the user with updateUser() (or addUser() if you prefer).
This is the most basic form of a user registration and it should not handle more.
Everything else would be handled through events:
- Sending welcome e-mails,
- Creating an invoice,
- etc...
Each of these event handlers may have their own interface as well.
-
Sure a few of his examples are a little far-fetched. I have never had the trouble distinguishing between what a Translator is or what a TranslatorInterface does.
I think SerializeToJson is a good descriptive name for the interface as would JsonSerializable I imagine.
But again this is probably a bad example because an object should not be aware of what JSON is nor how it "serializes" itself to it.
It gives you something to think about though the next time you develop a system. Proper naming is always half the battle.
Even though I prefer to write an interface without the suffix on small (individual) and private projects, on big projects I tend to stick to "open-source" standards
and that means *Interface suffix, *Trait suffix, and Abstract* prefix. This makes it easier to work together, and publish libraries.
-
I understand from your topics and posts in this forum that you have a – how do I say this nicely – rather relaxed attitude towards code quality. OK. This neither surprises nor troubles me.
But the topic is about learning proper OOP, and that's definitely not the right place for your hacks.
Is hardly a technical discussion.
-
I used to use the *Interface suffix until I read this article:
Suggest Me Top Artificial Intelligents Like ChatGpt
in Miscellaneous
Posted
I use GitHub Copilot daily, in my opinion it's an advanced autocomplete.
I have tried giving it prompts of what I want to achieve, but it doesn't understand your codebase, it just outputs a bunch of code it was trained on.
Like trying to use it to write SQL queries you get tables and columns that don't exist in your project. When trying to use it to write a class or function, it uses functions, libraries, and classes that don't exist.
Even though I try to stick to popular libraries and follow the PSR conventions, it still outputs abandoned libraries, non existing PHP functions etc..
Where it does shine is autocompletion, it understands the context surrounding it (array keys, arguments, variable names, ..), and your coding patterns.
It's also a bit slow, so often times I am waiting for a suggestion, get impatient and start typing, and then use the autocompletion when it's finally ready.
So, in conclusion, you still need to be a software developer to use it, and as long as you don't try to use it to write your entire project, it's okay. In the end, it saves you time writing or refactoring your code.