Jump to content

ignace

Moderators
  • Posts

    6,457
  • Joined

  • Last visited

  • Days Won

    26

Posts posted by ignace

  1. 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. 

    • Thanks 1
    1. 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.
    2. 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?
    3. 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.
    4. 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.

  2.  <?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, '', '&amp;'); //Did convert '&' to '&amp' 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 '&amp;', when echoing link here.
         } else {
             echo '<a href="' . $serps_url . '">' . "$i" . '</a>'; //No need to add htmlspecialchars(), to convert '&' to '&amp;', 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, '', '&amp;'); //Did convert '&' to '&amp' 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 '&amp;', when echoing link here.
         } else {
             echo '<a href="' . $serps_url . '">' . "$i" . '</a>'; //No need to add htmlspecialchars(), to convert '&' to '&amp;', 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]), '', '&amp;') ?>">
           <?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.

    • Great Answer 1
  3. 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;
    • Thanks 1
  4. 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).

    • Thanks 1
  5. 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...

    • Thanks 1
  6.  

    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.

    • Like 1
  7. As for your question on how the pros do it, this is how procedural programmers structure their project:

    1. load any required files
    2. get the inputs and validate them and take appropriate action if they are invalid
    3. do something with the input
    4. 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'; ?>

     

  8. 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.

    • Like 1
  9. 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')
    
    • Like 1
  10. 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);
    

    post-22476-0-74816000-1487582759_thumb.png

    post-22476-0-24703200-1487582766_thumb.png

  11. 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 :)

  12. 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);

  13. 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
    }

  14. 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?

  15. 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.

  16. 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.

  17. 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.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.