1
\$\begingroup\$

I'm new to object oriented PHP, so I'm sure of the good or bad practices. The below code is working perfectly, I just don't know if it is a bad or good approach to use most of the methods as static and declare class properties as public.

Could someone provide me some advise?

In users.php

<?php

class Users{

  public $user_id, $user_name, $user_email, $user_fname, $user_lname, $user_role;

  public static function display_users(){
      $display_users[] = self::find_all_query("SELECT * FROM users");
      return (!empty($display_users)) ? array_shift($display_users) : false;
  }

  public static function find_all_query($sql){
    global $database;
    $result = $database->query($sql);
    $db_result = array();
    while($row = mysqli_fetch_assoc($result)){
      $db_result[] = self::instantiation($row);
    }

    return $db_result;

  }

  public static function instantiation($row){

    $the_users_object = new Users();

    foreach ($row as $attribute => $data) {
      if($the_users_object->has_the_attribute($attribute)){
        $the_users_object->$attribute = $data;
      }
    }
    return $the_users_object;
  }

  private function has_the_attribute($attribute){
     $users_properties = get_object_vars($this);
     return array_key_exists($attribute , $users_properties);
  }


}
?>

In admin-users.php

<?php
$display_users = Users::display_users();

foreach ($display_users as $users) {
  echo "<tr>";
    echo "<td> $users->user_id    </td>";
    echo "<td> $users->user_name   </td>";
    echo "<td> $users->user_email </td>";
    echo "<td> $users->user_fname </td>";
    echo "<td> $users->user_lname </td>";
    echo "<td> $users->user_role </td>";
  echo "</tr>";
}
?>

In database.php

<?php
// require_once("init.php");

class Database{

  private $connection;

      function __construct(){
        $this->open_db_connection();

      }

      public function open_db_connection(){
        $this->connection = new mysqli(DB_HOST,DB_USER,DB_PASS,DB_NAME);

          if($this->connection -> connect_error){
            die('<h1>Database Connection Failed!!!!</h1>.'. $this->connection -> connect_error);
          }

      }


      private function checkQuery($query){
        if(!$query){
          die("<h1>Query Failed!!!!</h1> ". $this->connection -> error);
        }
      }

      public function query($sql){
        $result = mysqli_query($this->connection , $sql);
        $this->checkQuery($result);
        return $result;

      }

}

      $database = new Database();

?>
\$\endgroup\$

3 Answers 3

2
\$\begingroup\$

Some tips which might help you:

  1. Make as few static methods as possible. Those functions should not appear in any other function as they get in a way in unit tests. I usually use them to spit out html in views.
  2. Don't use global varibales.
  3. Hide as implementation as you can. Make public only those things that are vital for functionality.
  4. I consider new operator harmful as it cannot be mocked. Use dependency injection instead to provide object with all necessary data.

Last but not least I highly recommend book Clean Code by Robert C. Martin if you care about keeping your code clean.

\$\endgroup\$
2
\$\begingroup\$

I have several concerns with your classes.

The foremost one is that in that act of trying to define what a "user" is in your system, you are allowing arbitrary external SQL to be passed to the class to query the database. What if if someone passes SQL for a totally different table? Because of this, you really have no encapsulation around your User class at all. I would suggest perhaps a User class which truly represents represents only the properties and methods related to an individual user in the system, and then perhaps a UserFactory class which exposes methods to return one or more User objects. I think having the methods in the factory class be static makes a lot of sense. I don't know that having static methods on an instantiable User class makes much sense.

I don't understand your need for the database class. It doesn't really do anything. Normally, one might write a database wrapper class to do something like manage DB connections, abstract away underlying DB implementation (i.e. mysqli in this case) from consuming classes, provide more natural language query capabilities, perform general object-relational mapping or something that actually adds value. This class:

  • doesn't manage connections (it looks like an incomplete singleton implementation)
  • it leaks implementation outside the class (you potentially return mysqli_result to caller )
  • it outputs errors to the end user (why would a DB class do this?)
  • is constructed oddly, mixing OO and procedural ways of working with mysqli
  • is extremely limited in providing any any value-added querying capabilities

I honestly don't see what value this brings as opposed to just letting classes work directly with mysqli instances (or a common instance).

Below, I walk through the code and give more specific thoughts. My comments are within multi-line comments. One common comment I would make is that your code has no comments in it at all. Comments are just as important as the code when it comes to maintaining a code base. You should have comments in cases where there may be question as to what the intent of a certain section of code is. One can easily over-comment their code, but I would rather someone who is starting out do that than have no comments at all. You will eventually learn through practice when comments are most appropriate.

users.php

<?php

/*
Add space after Users and perhaps consider moving opening bracket to next line
which is a common stylguide approach for PHP classes. If you ultimately
intend for an instantiated object of this class to represent a single user,
then singular User would be a more appropriate name.
*/
class Users{

/*
Put each property declaration on its own line. It makes you code easier to read.
*/
  public $user_id, $user_name, $user_email, $user_fname, $user_lname, $user_role;

/*
This method doesn't display anything. Perhaps better name would be
get_all_users() or similar.  I don't know that this method adds
much value beyond what is in find_all_query below.  I think in this
case these methods could actually be combined so it is clear
what you are doing here (i.e. your SQL is not in a separate place).
Add space before opening function bracket (typical throughout code).
*/
  public static function display_users(){
/*
Should is not just be
$display_users = self::find_all_query(...); ?  Why add another level of 
array nesting?
Don't use SELECT *. It is wasteful of resources and makes your code more fragile
to database schema changes in the future.  Also, if you select only the fields
you want to put into the object, then you can do away with your
has_the_attribute method altogether.
*/
      $display_users[] = self::find_all_query("SELECT * FROM users");
/*
Why ternary here?  This line of code is hard to read. Split onto multiple lines.
This only handles happy path. If you fix the line of code above around setting
$display_users, you don't need to do array_shift().
Also, consider returning empty array here instead of false, for case when no
records are found and let the caller deal with it.
*/
      return (!empty($display_users)) ? array_shift($display_users) : false;
  }

/*
If this is only meant to be called by the display_users method, then make this
protected/private. Again, I don't see great need to split these two methods
apart. What you have done here is provide a way for any place in the code to 
execute arbitrary SQL against the database.  A very bad idea.
*/
  public static function find_all_query($sql){
/*
Don't use globals. Ever. It is a really poor programming practice that PHP made
way too easy to do in its early days and the rest of the world still has to deal
with it.  Just provide the class the dependencies it needs to operate, ideally
through the practice of dependency injection. For example in this case, you
could simply pass your DB connection to this method as a parameter.
You are doing nothing to validate that the parameter passed is what is expected
(i.e. a non-zero length string). You should always validate first and stop method execution for all public methods that accept parameters.
*/
    global $database;
/*
This is happy path only.  What if query fails?
*/
    $result = $database->query($sql);
    $db_result = array();
/*
Here is great example of how your DB class leaks implementation.  Why is that class not returning an array, instead requiring this class method to understand
that the result from query is mysqli_result.  Don't mix OO and procedural.
*/
    while($row = mysqli_fetch_assoc($result)){
      $db_result[] = self::instantiation($row);
    }

    return $db_result;

  }

/*
Why is this public?  You want any place in code to be able to pass an
associative array to create a user object?  Why is this not a constructor?
If you truly want this to be public method, you need to validate $row is a 
non-empty array before working with it.
*/
  public static function instantiation($row){

    $the_users_object = new Users();

    foreach ($row as $attribute => $data) {
/*
Here if you do your SQL select properly, you have no need to check values in
$row every single time, as you KNOW you have the proper fields configured.
*/
      if($the_users_object->has_the_attribute($attribute)){
        $the_users_object->$attribute = $data;
      }
    }
    return $the_users_object;
  }
/*
This function really has no value given other comments.
*/
  private function has_the_attribute($attribute){
     $users_properties = get_object_vars($this);
     return array_key_exists($attribute , $users_properties);
  }


}
?>

database.php

<?php
// require_once("init.php");

/*
Same formatting comment as for user class.
*/
class Database{
/*
Bad indentation here.
If you are trying to make singleton, this should be static.
*/
  private $connection;

/*
If you are trying to make singleton, the constructor must be private.
*/
      function __construct(){
        $this->open_db_connection();
/*
Remove whitespace line
*/

      }

/*
If you are making singleton, this method should be static as there should
be no need to instantiate this class in order to get a connection. Also
if making singleton, this method should check for previous existence of
a connection being stored on the class and simply return it rather than
always creating a new connection.
*/
      public function open_db_connection(){
/*
Bad indentation This is happy path.  You should validate you have valid
mysqli object instantiated before assigned to connection.
*/
        $this->connection = new mysqli(DB_HOST,DB_USER,DB_PASS,DB_NAME);
/*
Don't die from within this code and defintely don't leak your error messages to
end users.  Perhaps log the error here and throw and exception to let calling
code figure out what to do (i.e. provide messaging to user). A database class
should know nothing about end user messaging.
*/
          if($this->connection -> connect_error){
            die('<h1>Database Connection Failed!!!!</h1>.'. $this->connection -> connect_error);
          }

      }

/*
This seems like sort of a trivial method. What value does it really add? How
flexible is this to different query use cases?  Again, see notes above about
error messaging.
*/
      private function checkQuery($query){
        if(!$query){
          die("<h1>Query Failed!!!!</h1> ". $this->connection -> error);
        }
      }

/*
Validate input. What value does this method really add to code using this object?
*/
      public function query($sql){
        $result = mysqli_query($this->connection , $sql);
        $this->checkQuery($result);
        return $result;
/*
Remove extra line
*/

      }

}
\$\endgroup\$
1
\$\begingroup\$

Welcome to code review. Nice first question!

Global Variables
xReprisal provides a good answer. The first 2 bullets are very good advice, I might have switched the order, You should avoid global variables as much as possible. In more formal programming languages such as C, C++, C# and Java they should be avoided completely. Global variables make a program very easy to break and very hard to debug. In very large programs it is possible that there can be global variables with the same name used for completely different purposes.

Readability and Maintainability
The code is already very readable and maintainable, but there are a few inconsistencies in the style.

Maintain the same indentation for all code

This code is indented differently than all of the other code:

  public static function display_users(){
      $display_users[] = self::find_all_query("SELECT * FROM users");
      return (!empty($display_users)) ? array_shift($display_users) : false;
  }

You have a number of functions where the closing } is down one or more lines, and then you have functions where the closing } immediately follows the last line of code. Pick one method and stick with it.

Programming Principles that Apply You have done a pretty good job of this already, and you may be aware of some or all of these programming principles. Here are a few good programming principles that apply:

Single Responsibility Principle:
The single responsibility principle (SRP) states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility.

Demeters's Law:
The Law of Demeter (LoD) or principle of least knowledge is a design guideline for developing software, particularly object-oriented programs. In its general form, the LoD is a specific case of loose coupling. The guideline was proposed at Northeastern University towards the end of 1987, and can be succinctly summarized in each of the following ways:

  1. Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
  2. Each unit should only talk to its friends; don't talk to strangers.
  3. Only talk to your immediate friends.

Don't Repeat Yourself:
The DRY principle is stated as "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system". The principle has been formulated by Andy Hunt and Dave Thomas in their book The Pragmatic Programmer. They apply it quite broadly to include "database schemas, test plans, the build system, even documentation."

When the DRY principle is applied successfully, a modification of any single element of a system does not require a change in other logically unrelated elements. Additionally, elements that are logically related all change predictably and uniformly, and are thus kept in sync. Besides using methods and subroutines in their code, Thomas and Hunt rely on code generators, automatic build systems, and scripting languages to observe the DRY principle across layers.

SOLID Programming Principle:
In computer programming, SOLID (single responsibility, open-closed, Liskov substitution, interface segregation and dependency inversion) is a mnemonic acronym introduced by Michael Feathers for the "first five principles" named by Robert C. Martin in the early 2000s that stands for five basic principles of object-oriented programming and design. The intention is that these principles, when applied together, will make it more likely that a programmer will create a system that is easy to maintain and extend over time. The principles of SOLID are guidelines that can be applied while working on software to remove code smells by providing a framework through which the programmer may refactor the software's source code until it is both legible and extensible. It is part of an overall strategy of agile and Adaptive Software Development.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.