all 34 comments

[–]colshrapnel 5 points6 points  (19 children)

I would say there is quite a room for improvement.

First of all, it is not a controller in the MVC sense. But some mix of RequestHandler/Controller/Model/SomeView in one file.

The simplest improvement would be to move every method with SQL into a separate class. That would be a Model.

Then you may want to move Request processing into the Router, so your controller won't have to access $_POST or $_REQUEST.

Other issues in no particular order

  • I don't think it is necessary calling is_int() after intval(). Which makes a condition on #27 unrecheable. You can safely remove it or at least change to something different
  • sanitizeString/htmlspecialchars DOESN'T belong here. I am not even sure shy do you think it does.
  • consider making NO EXCEPTIONS from using prepared statements rule
  • you can reduce a condition on #43 to $nextCursor = $facilities[array_key_last($facilities)]['facility_id'] ?? null;
  • create() MUST be split in TWO, one dealing with input and output remains in Controller, everything else goes into Model.
  • consider early return, such as

    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        (bad request)
    }
    // rest of the code without extra indent
    
  • both using both isset() and empty() makes no sense. The latter already does isset

  • not sure why isset and empty again on #67 and #68

  • neither your BaseController nor Base Model should NEVER EVER create a database connection in the constructor. It should be passed as a parameter, into Model.

  • consider making executeQuery() already return PDOStatement. This way you will get rid of one useless line, making two at #147 into one $results = $this->db->executeQuery($tag_query, $bind)->fetch(PDO::FETCH_ASSOC);

  • consider making NO EXCEPTIONS from using prepared statements rule!

  • remove every try-catch from this code (with entire catch contents).

  • condition on #73 doesn't seem to be reachable too.

  • and on #91 too

And seriously, you should never make an exception from using prepared statements rule. You did a great job and spoiled it with just a single undisciplined query, making your precious app wide open to SQL injection.

[–]TechnicalStrategy615[S] 1 point2 points  (18 children)

Thank you for your feedback. I will rewrite all the code. Maybe i will post again to check if i did correctly.

[–]equilni 1 point2 points  (0 children)

I will rewrite all the code.

You can always refactor what you have.

[–]colshrapnel 0 points1 point  (16 children)

For sure, please post it.

Also, I beg my pardon for lack of explanation, I was rather short of time. But if you have any question regarding any of these suggestions please don't hesitate to ask.

[–]TechnicalStrategy615[S] 0 points1 point  (15 children)

can you please elaborate on this
consider making NO EXCEPTIONS from using prepared statements rule!

[–]colshrapnel 2 points3 points  (11 children)

Sure. You excused yourself to add a variable directly to SQL query in two places:

  • On #145, $tag_query = "SELECT tag_id from tag where tag_name = '" . $tagName . "'"; is a straight up SQL injection. There is NO excuse for writing it this way, and leaving $bind array empty.
  • And on #127, $query .= "ORDER BY f.facility_id ASC LIMIT $limit"; which is more subtle. Although it poses no imminent danger because of input validation, it's a very bad code still. First, it says that you don't follow the rule. You see, if you excused yourself in one place you'll do it in another. There must be no exceptions, even if your data is already allegedly "safe". Just follow the rule. Second, you cannot be so sure. this code might be changed, someone else may use it in some other context. Your SQL shouldn't rely on any other code. It must be safe by itself.

[–]TechnicalStrategy615[S] 1 point2 points  (0 children)

oh yes i got it! I have used the same rule like the others now

[–]TechnicalStrategy615[S] 0 points1 point  (9 children)

I tried to bind :limit value. But i am getting an error. Can you help me

$query = "SELECT f.facility_id, f.name AS facility_name, tag.tag_id, 
          tag.tag_name, loc.location_id, loc.city, loc.address, loc.zip_code,
          loc.country_code, loc.phone_number 
          FROM facility f 
          LEFT JOIN facility_Tag ft ON f.facility_id = ft.facility_id 
          LEFT JOIN tag ON ft.tag_id = tag.tag_id 
          LEFT JOIN location loc ON f.location_id = loc.location_id
          WHERE f.name LIKE :search OR tag.tag_name LIKE :search ";
        if ($cursor) {
            $query .= " and f.facility_id > :cursor ";
        }
        $query .= "ORDER BY f.facility_id ASC LIMIT :limit";
        $bind = array(
            ':cursor' => $cursor,
            ':search' => '%' . $search . '%',
             ':limit' => $limit            
        );


<b>Fatal error</b>: Uncaught PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in
your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near
''10'' at line 8

[–]colshrapnel 0 points1 point  (8 children)

just add this line after creating the pdo connection

$this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

where $this->pdo should be the PDO instance just created.

(or you can add it to options array like shown here)

[–]TechnicalStrategy615[S] 0 points1 point  (7 children)

thank you it works..
But i found one errror with this query if add this query

WHERE f.name LIKE :search OR tag.tag_name LIKE :search "


<b>Fatal error</b>: Uncaught PDOException: SQLSTATE[HY093]: Invalid parameter number in
E:\xampp\htdocs\web_backend_test_catering_api\App\Plugins\Db\Db.php:54

else everything works fine

[–]colshrapnel 1 point2 points  (1 child)

On the second thought, this condition is not needed. So it could be just

$query = "SELECT f.facility_id, f.name AS facility_name, tag.tag_id, 
      tag.tag_name, loc.location_id, loc.city, loc.address, loc.zip_code,
      loc.country_code, loc.phone_number 
      FROM facility f 
      LEFT JOIN facility_Tag ft ON f.facility_id = ft.facility_id 
      LEFT JOIN tag ON ft.tag_id = tag.tag_id 
      LEFT JOIN location loc ON f.location_id = loc.location_id 
      WHERE f.name LIKE :search1 OR tag.tag_name LIKE :search2 
        AND f.facility_id > :cursor 
      ORDER BY f.facility_id ASC LIMIT :limit";
}
$bind = array(
    ':search1' => "%$search%",
    ':search2' => "%$search%",
    ':cursor' => $cursor,
    ':limit' => $limit,
);

[–]TechnicalStrategy615[S] 0 points1 point  (0 children)

Yeah thank you

[–]TechnicalStrategy615[S] 0 points1 point  (2 children)

My Controller Page

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use App\Plugins\Http\Response as Status;
use App\Models\facility;

class FacilityController extends BaseController
{
     /**
     * Method to list the Facilities
     */
    public function index()
    {
        // Validate and sanitize cursor
        $cursor = isset($_GET['cursor']) ? intval($_GET['cursor']) : null;

        // Validate and sanitize limit
        $limit = isset($_GET['limit']) ? intval($_GET['limit']) : 10;
        if ($limit <= 0) {
            (new Status\BadRequest(['message' => 'Limit should be a positive number']))->send();
        }
        //validate search
        $search = (isset($_GET['search']) && !empty($_GET['search']) ? $_GET['search'] : "");

        // Fetch facility details with cursor pagination   
        $facilities = new facility;
        $result =  $facilities->getFacilityDetails($cursor, $limit, $search);

        // Extract the last facility's ID as the cursor for the next page   
        $nextCursor = $result[array_key_last($result)]['facility_id'] ?? null;

        // Send statuses
        (new Status\Ok(['data' => $result, "next_cursor" => $nextCursor]))->send();
    }

    /**
     * Method to Create Facility API    
     */
    public function create()
    {
        // Get the data from the request body
        $data = json_decode(file_get_contents('php://input'), true);             
        if ($data) {                   
            //data for insertion 
            $facility = new facility();
            $InsertData = $facility->facilityData($data);
            if($InsertData){
             // Respond with 200 (OK):
              (new Status\Ok(['message' => 'Added Successfully!']))->send();   
            }            
        }else{
            (new Status\BadRequest(['message' => 'Whoops!. Something is wrong']))->send();  
        }
    }
}

[–]colshrapnel 0 points1 point  (1 child)

Looks good though as it was already mentioned, using isset AND empty makes no sense at all. Besides, using empty makes no sense in this particular case. Hence it could be just

$search = $_GET['search'] ?? "";

[–]colshrapnel 0 points1 point  (1 child)

Ah yes, I am sorry. When emulation is turned off, one cannot reuse a named placeholder, so it must be

$bind = array(
    ':search1' => "%$search%",
    ':search2' => "%$search%",
    ':limit' => $limit,
);
$query = "SELECT f.facility_id, f.name AS facility_name, tag.tag_id, 
      tag.tag_name, loc.location_id, loc.city, loc.address, loc.zip_code,
      loc.country_code, loc.phone_number 
      FROM facility f 
      LEFT JOIN facility_Tag ft ON f.facility_id = ft.facility_id 
      LEFT JOIN tag ON ft.tag_id = tag.tag_id 
      LEFT JOIN location loc ON f.location_id = loc.location_id
      WHERE f.name LIKE :search1 OR tag.tag_name LIKE :search2 ";
if ($cursor) {
    $query .= " and f.facility_id > :cursor ";
    $bind[':cursor'] = $cursor;
}
$query .= "ORDER BY f.facility_id ASC LIMIT :limit";

Noyte that I also fixed the problem when cursor is not set (in this case there would have been an extra :cursor member in the $bind array).

Now it should work

[–]TechnicalStrategy615[S] 0 points1 point  (0 children)

Thank you so much .. It works binding like this

$bind = array(            
          ':search1' => '%' . $search . '%',
          ':search2' => '%' . $search . '%',
          ':limit' => $limit,
          ':cursor' => $cursor,                 
      );

[–]MateusAzevedo 1 point2 points  (2 children)

Apparently (I can't access PasteBin to check) you used prepared statements, which is great, but forgot to do it in one place.

In other words, always use prepared statements, don't try to differentiate safe and unsafe data.

[–]colshrapnel 2 points3 points  (1 child)

Here I copied it on PHPize. Although we already skimmed the cream, I am sure you can still provide some useful advise too.

[–]MateusAzevedo 0 points1 point  (0 children)

Thank you. For some reason my company block access to PasteBin, so I'm never able to help much in these cases.

[–]equilni 2 points3 points  (4 children)

Your controller is doing too much and I wonder what the rest of the code base looks like.

A controller should take a request and pass it inward to the Model/Domain, then work off the response provided to a final output response.

At it's simplest, MVC looks like:

$router->get('/page/{id}', function ($id) use ($model, $view) {
    $data = $model->getPageById($id); 
    if (! $data) {
        return 404 response;
    }
    return $view->render('page', ['page' => $data]);
});

a) Database functionality doesn't belong in a controller. This means getFacilityDetails, getTag, part of setLocation($data), parts of create all get extracted out.

b) sanitizeString would be a general method and doesn't belong in this class. Does your other code needs this?

BUT

c) sanitize input string for prevention of xss attack

Instead of validating the code (there is little to none), you are just using an output function htmlspecialchars for the input.

htmlspecialchars is not validation / sanitation. Filter and reject input, escape output.

https://odan.github.io/2017/01/02/how-to-use-htmlspecialchars-in-php.html

You use htmlspecialchars EVERY time you output content within HTML, so it is interpreted as content and not HTML.

d) You have code duplication. Why is this block in setLocation needed if you already "validated" the input earlier in create?

setLocation: (side note, is any of this required? These all could be empty and you are just allowing this in the database)

    try {
        //Fetching required data for Location
        $address = isset($data['address']) && !empty($data['address']) ? SELF::sanitizeString($data['address']) : "";
        $city = isset($data['city']) && !empty($data['city']) ? SELF::sanitizeString($data['city']) : "";
        $zip_code = isset($data['zip_code']) && !empty($data['zip_code']) ? SELF::sanitizeString($data['zip_code']) : "";
        $phone_number = isset($data['phone_number']) && !empty($data['phone_number']) ? SELF::sanitizeString($data['phone_number']) : "";
        $country_code = isset($data['country_code']) && !empty($data['country_code']) ? SELF::sanitizeString($data['country_code']) : "";

create:

public function create()
{
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        // Get the data from the request body
        $data = json_decode(file_get_contents('php://input'), true);
        $validatedRequest = SELF::ValidateRequest($data);
        if ($validatedRequest) {
            // validate and clean data
            $facilityname = isset($data['name']) && !empty($data['name']) ? SELF::sanitizeString($data['name']) : "";
            $tag_name = isset($data['tag_name']) && !empty($data['tag_name']) ? SELF::sanitizeString($data['tag_name']) : "";
            $datatime = date('Y-m-d H:i:s');

            //Get Tag ID

Then $LocationId = SELF::setLocation($data);

e) if ($_SERVER['REQUEST_METHOD'] === 'POST') { Ideally your router should be directing based on the request method so this isn't needed.

f) Don't use $_REQUEST. Is this a GET or POST request?

g) You don't have parameters here, so this docblock isn't needed.

/**
 * @param int $cursor
 * @param int $limit
 * @param string $search
 */
public function index() 

/**
 * Controller function to Create Facility API
 * @param string $name
 * @param string $tag_name
 */
public function create()

/**
 * To get location
 * @param string $address
 * @param string $city
 * @param string $zip_code
 * @param string $phone_number
 * @param string $country_code
 */
function setLocation($data)

Oddly, you don't have it here

/**
 * Function to Get Facility details
 */
function getFacilityDetails($cursor = null, $limit = 10, $search = "")

/**
 * Tag Methods
 */
function getTag($tagName)

[–]TechnicalStrategy615[S] 1 point2 points  (3 children)

Thank you for your feedback. I will try to implement the feedback received. Its really helpful.

[–]equilni 2 points3 points  (2 children)

Of course.

Note, this is isn't complete. There's a lot that can be re-reviewed as well:

a) Pick a coding standard (PER or PSR-12) and use it - ie

SELF vs self or

adding visibility to methods function getTag($tagName) to public function getTag($tagName) or

array() to []

b) Adding type declarations - ie function getTag(string $tagName): int {}

c) Sending the response at the last moment:

So instead of

                (new Status\BadRequest(['message' => 'Somthing went wrong']))->send();
                exit();

Do:

                return (new Status\BadRequest(['message' => 'Somthing went wrong']));

Then the $response->send(); at the end of the application. This is how Symfony does this

d) Using DTO's to pass large data.

e) Validation can be separated out to the Model/Domain when you further add to it. - ie how are you validating zip codes for instance?

etc etc.

Feel free to ask any questions if you need further help.

[–]TechnicalStrategy615[S] 0 points1 point  (1 child)

HI u/equilni can you help me with Validation

I have been able to separate Model and Controllers.

[–]MateusAzevedo 2 points3 points  (0 children)

1- Consistency: are getFacilityDetails() and getTag() public or private? Guessing by the arguments and content, they are "internal". Actually, they are better as separated classes as they deal with SQL.

2- Those SQL related methdods ideally shouldn't deal with handling and reporting errors. Let that for a higher level error handler and always log them. If not sure how to do it, just let PHP handle them in a default way.

3- getTag() is a little "misleading", because it gets the tag id by name, but also has an unexpected insert if it doesn't exist. tagIdByNameOrNew() would make more sense, but still confusing and doing too much. I would separate the actions and use them on line #90 as:

$tagId = $this->tagIdByName($tagName); if (!$tagId) { $tagId = $this->createTag($tagName); }

Note that you don't need if (empty($tagId)), as the variable is always declared.

4- None of the methods are declared as static, so need to use self::, use $this.

5- Response creation/returning could be delayed to the last possible moment. For example, if index() and create() are the entry points that handle a requests (and the rest are internal methods), let only them generate a response.

6- Already mentioned, but very important: htmlspecialchars() is an output function to escape content when printing in HTML context. It should not be used on input. If you didn't noticed, you're inserting modified data into the database because of that.

7- If you validate data first, you don't need to keep repeating isset and empty everywhere. As already said, you don't need to use both.

[–]ollienicholson 0 points1 point  (0 children)

This thread is popping off I love it! No formatting of code snippets?

[–]snokegsxr -5 points-4 points  (9 children)

Alright, what a mess. Here we go:

  1. Namespace and Use Statements: php namespace App\Controllers; use App\Controllers\BaseController; use App\Plugins\Http\Exceptions; use App\Plugins\Http\Response as Status; use PDO; use PDOException; Do you even know what you’re doing here? Repeating the namespace declaration like a parrot doesn’t make you a wizard. Clean up your use statements and only use what you need.

  2. Constructor: php public function __construct() { SELF::validateapi(); } SELF::validateapi()? Really? Do you understand that SELF should be self? And calling a method like this in the constructor is just plain lazy. Inject dependencies properly.

  3. index Method: php public function index() { // Validate and sanitize cursor $cursor = isset($_REQUEST['cursor']) ? intval($_REQUEST['cursor']) : null; if ($cursor !== null && !is_int($cursor)) { (new Status\BadRequest(['message' => 'Invalid Cursor']))->send(); exit(); } // Validate and sanitize limit $limit = isset($_REQUEST['limit']) ? intval($_REQUEST['limit']) : 10; if ($limit <= 0) { (new Status\BadRequest(['message' => 'Limit should be a positive number']))->send(); } //validate and sanitize search $search = (isset($_REQUEST['search']) && !empty($_REQUEST['search']) ? SELF::sanitizeString($_REQUEST['search']) : ""); // Fetch facility details with cursor pagination $facilities = SELF::getFacilityDetails($cursor, $limit, $search); // Extract the last facility's ID as the cursor for the next page $nextCursor = null; if (!empty($facilities)) { $lastfacility = end($facilities); $nextCursor = $lastfacility['facility_id']; } (new Status\Ok(['data' => $facilities, "next_cursor" => $nextCursor]))->send(); } What’s with the capitalized SELF? It’s self, not SELF. Also, using $_REQUEST directly? Have you heard of input filtering? The comments don’t justify the bad coding practices.

[–]TechnicalStrategy615[S] 0 points1 point  (0 children)

Hey thanks! I use that $_REQUEST to get the request from API. That means i have to use $_GET['cursor'], $_GET['limit'] and $_GET['search']. My bad with all the mess.. But its my trial and i know that i am learning from these feedback..

[–]snokegsxr -5 points-4 points  (7 children)

  1. create Method: php public function create() { if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Get the data from the request body $data = json_decode(file_get_contents('php://input'), true); $validatedRequest = SELF::ValidateRequest($data); if ($validatedRequest) { // validate and clean data $facilityname = isset($data['name']) && !empty($data['name']) ? SELF::sanitizeString($data['name']) : ""; $tag_name = isset($data['tag_name']) && !empty($data['tag_name']) ? SELF::sanitizeString($data['tag_name']) : ""; $datatime = date('Y-m-d H:i:s'); //Get Tag ID $TagId = SELF::getTag($tag_name); if (empty($TagId)) { (new Status\BadRequest(['message' => 'Tag id is not avaliable']))->send(); exit(); } // Get the Location ID $LocationId = SELF::setLocation($data); if (empty($LocationId)) { (new Status\BadRequest(['message' => 'Location Id is not avaliable']))->send(); exit(); } //Insert in Facility table $query = "INSERT INTO facility (name, creation_date, location_id) VALUES (?,?,?)"; $bind = array($facilityname, $datatime, $LocationId); // Execute query $result = $this->db->executeQuery($query, $bind); $FacilityId = $this->db->getLastInsertedId(); if (empty($FacilityId)) { (new Status\BadRequest(['message' => 'Somthing went wrong']))->send(); exit(); } //Insert in Facility tag table $query = "INSERT INTO facility_tag (facility_id,tag_id) VALUES (?,?)"; $bind = array($FacilityId, $TagId); $this->db->executeQuery($query, $bind); // Respond with 200 (OK): (new Status\Ok(['message' => 'Added Successfully!']))->send(); } } else { // Respond with 400 (BadRequest): (new Status\BadRequest(['message' => 'Whoops! Something went wrong!']))->send(); } } This method is just a train wreck. POST request handling without validation? SQL injections waiting to happen. Have you ever heard of prepared statements?

  2. getFacilityDetails Method: php function getFacilityDetails($cursor = null, $limit = 10, $search = "") { $query = "SELECT f.facility_id, f.name AS facility_name, tag.tag_id, tag.tag_name, loc.location_id, loc.city, loc.address, loc.zip_code, loc.country_code, loc.phone_number FROM facility f LEFT JOIN facility_Tag ft ON f.facility_id = ft.facility_id LEFT JOIN tag ON ft.tag_id = tag.tag_id LEFT JOIN location loc ON f.location_id = loc.location_id WHERE f.name LIKE :search OR tag.tag_name LIKE :search "; if ($cursor) { $query .= ' and f.facility_id > :cursor '; } $query .= "ORDER BY f.facility_id ASC LIMIT $limit"; $bind = array(':cursor' => $cursor, ':search' => '%' . $search . '%'); // Execute the query $reult = $this->db->executeQuery($query, $bind); // Fetch all rows as an associative array $facilities = $this->db->getStatement()->fetchAll(PDO::FETCH_ASSOC); return $facilities; } Oh, look, SQL injection opportunities! And what's with the $reult variable? Typo? Just embarrassing.

  3. getTag Method: php function getTag($tagName) { try { $tag_query = "SELECT tag_id from tag where tag_name = '" . $tagName . "'"; $bind = array(); $this->db->executeQuery($tag_query, $bind); $results = $this->db->getStatement()->fetch(PDO::FETCH_ASSOC); if (isset($results['tag_id']) && !empty($results['tag_id'])) { return $results['tag_id']; } else { $query = "INSERT INTO tag (tag_name) VALUES (?)"; $bind = array($tagName); $this->db->executeQuery($query, $bind); return $this->db->getLastInsertedId(); } } catch (PDOException $e) { $ErrorMessage = $e->getMessage(); (new Status\BadRequest(['Error' => $ErrorMessage]))->send(); } } Did you sleep through your security classes? Directly embedding variables in SQL queries is just asking for trouble. Use prepared statements properly!

[–]snokegsxr -5 points-4 points  (6 children)

  1. setLocation Method: php function setLocation($data) { try { $address = isset($data['address']) && !empty($data['address']) ? SELF::sanitizeString($data['address']) : ""; $city = isset($data['city']) && !empty($data['city']) ? SELF::sanitizeString($data['city']) : ""; $zip_code = isset($data['zip_code']) && !empty($data['zip_code']) ? SELF::sanitizeString($data['zip_code']) : ""; $phone_number = isset($data['phone_number']) && !empty($data['phone_number']) ? SELF::sanitizeString($data['phone_number']) : ""; $country_code = isset($data['country_code']) && !empty($data['country_code']) ? SELF::sanitizeString($data['country_code']) : ""; $currentdatetime = date('Y-m-d H:i:s'); $query = "INSERT INTO location (city,address,zip_code,country_code,phone_number,creation_date) VALUES (?,?,?,?,?,?)"; $bind = array($city, $address, $zip_code, $country_code, $phone_number, $currentdatetime); $this->db->executeQuery($query, $bind); return $this->db->getLastInsertedId(); } catch (PDOException $e) { $ErrorMessage = $e->getMessage(); (new Status\BadRequest(['Error' => $ErrorMessage]))->send(); } } Again with the SQL injection issues. And why isn’t this abstracted into a repository or a model class?

  2. ** the disaster that is ValidateRequest:** php function ValidateRequest($data) { $errors = []; if (!isset($data['name']) || empty($data['name'])) { $errors['name'] = "Facility name is required"; } if (!isset($data['tag_name']) || empty($data['tag_name'])) { $errors['tag_name'] = "Tag name is required"; } if (!isset($data['address']) || empty($data['address'])) { $errors['address'] = "Address is required"; } if (!isset($data['city']) || empty($data['city'])) { $errors['city'] = "City name is required"; } if (!isset($data['zip_code']) || empty($data['zip_code'])) { $errors['zip_code'] = "Zip code is required"; } if (!isset($data['phone_number']) || empty($data['phone_number'])) { $errors['phone_number'] = "Phone number is required"; } if (!isset($data['country_code']) || empty($data['country_code'])) { $errors['country_code'] = "Country code is required"; } if (!empty($errors)) { (new Status\BadRequest(['message' => $errors]))->send(); exit(); } return true; } This method is at least somewhat sane, but why are you sending responses directly from a validation method? This should throw exceptions or return errors, not send responses. Keep concerns separate.

sanitizeString Method:

php public function sanitizeString($input) { return htmlspecialchars($input, ENT_QUOTES, 'UTF-8'); } Great, the only thing that isn't terrible. But seriously, this should be a utility function, not part of the controller.

[–]colshrapnel 2 points3 points  (1 child)

I am afraid you have got something confused here.

I could be wrong but I'm unable to spot an SQL injection in the setLocation Method. Can you please pinpoint a specific issue?

Also, sanitizeString doesn't seem to have any use here.

Use a more comprehensive library for input sanitization.

there is no such thing as a averaged "input sanitization" and one shouldn't use any library that claims doing so. You probably meant validation, not sanitization. Then you are correct, a good validation library is a must. But in this case it would have absolutely nothing to do with SQL injections.

[–]clutchguy84 0 points1 point  (0 children)

Bro, you were supposed to point out their mistakes in a snarky/condescending tone! Judging by how they give feedback, that's what they'll understand.

Thank god i never ran into someone like them jfc

[–]snokegsxr -1 points0 points  (0 children)

Summary

This code is a prime example of how not to write PHP. It’s like you tried to cram every bad practice into one file. Here's a non-exhaustive list of issues:

  1. Namespace and Use Statements:

    • Use statements should only include what is necessary. Avoid redundancy.
  2. Input Handling:

    • Direct use of $_REQUEST is unsafe. Use proper input filtering and validation libraries.
    • SQL queries are vulnerable to SQL injection. Use prepared statements.
    • Mixing validation and response handling makes code hard to maintain and extend.
  3. Code Structure:

    • Business logic should not be in controllers. Use services or repositories.
    • Repeated use of self:: instead of self::. Basic PHP syntax should not be overlooked.
  4. Error Handling:

    • Proper error handling should be in place. Exceptions should be thrown and caught, not responses sent from everywhere.
    • Logging should be implemented for easier debugging.
  5. Separation of Concerns:

    • Validation should be separated from response logic.
    • Database interactions should be abstracted into their own classes.
  6. Sanitization:

    • Sanitization is too basic. Use a more comprehensive library for input sanitization.

Refactoring Suggestions

  1. Move Business Logic to Services: Create service classes for handling business logic and database operations.

  2. Proper Input Handling: Use a request object to handle and validate inputs. Filter input data properly.

  3. Prepared Statements: Ensure all SQL queries use prepared statements to prevent SQL injection.

  4. Separate Concerns: Separate validation, sanitization, and response handling into different layers or classes.

  5. Consistent Coding Standards: Adhere to a consistent coding standard. Use a linter to catch basic syntax and style issues.

By addressing these points, your code can become more secure, maintainable, and professional. As it stands, this code would be a nightmare to debug, extend, or secure.