PHP: The User Class Snippet

Okay, this is gonna be fun. I just finished my own version of the User class in php. I know there are millions of others around, but I didn’t find one that would suit my project – they’re all too complicated and unflexible. I’ll start off by explaining some of the requirements and a couple of examples. I’ll introduce you to the class at the end. I had to slice the article with a cut in case someone isn’t interested at all. There’s way too much code here to display on my main page. So if you’re interested, read on…

The User class uses two tables in a MySQL database – the users table and the users meta table. You can go ahead and use just one (omitting the meta functions of the class), but I do encourage you to split up data that is needed on every run, and data that’s only required when viewing a user’s profile for instance. This will give you better performance. Just a short example for fields in the users table: id, username, fullname and avatar. Ones that may go into the users meta table: id, password, email, location, icq, url, etc. See the difference?

Anyways, my class assumes that you have the id and username fields in your users table, and id field in the users meta table (in case you want to use that). The ids must be uniqe in tables scopes and valid cross-table (i.e. user1’s id in both users and users meta tables should be 1). I prefer using a MySQL UNIQUE index on both tables and a AUTO_INCREMENT parameter for the users table. Kick me if I’m wrong.

Off with the examples:

// just an instance of the class, passing the mysql link
// as the first and only parameter
$user1 = new User($db);

// we initialize a user here whos id is 1
$user2 = new User($db, 1)

// same as above, but we user the username instead, and we load his meta data too
// the last two parameters indicate that we'll be using the my_users and my_users_meta
// tables instead of the default ones (you may change the defaults in the class)
$user3 = new User($db, "some_username", true, "my_users", "my_users_meta");

// load some_username into $user1 together with his meta
$user1->load("some_username", true);

// $user2 had user #1 loaded, we load in his meta this way
$user2->loadMeta();

// forget user #3
$user3->clear();

// output some data
echo $user1->username . "'s id is #" . $user1->id;
echo "He is " . $user1->age . " years old."; // in case you have an "age" field in the users table

echo "Some meta information perhaps: " . $user1->meta["icq"]; // must have an "icq" field in users meta table

// change some data
$user1->fullname = "Jack Sparrow";
$user1->meta["profession"] = "Pirate";

// save it to database
$user1->save();
$user1->saveMeta();

// or just
$user1->save(true); // which saves meta as well

// Let's create a couple of new users
$user1->create("john");
$user1->fullname = "John Smith";
$user1->save();

$user2->create("george", true);
$user2->fullname = "George Black";
$user2->data["meta"]["icq"] = 12345;
$user2->save(true);

I guess that’s enough. Let’s look at our class now.

class User
{
	protected $db;			// Ugh, database link
	protected $tb_users;	// The users table, duh
	protected $tb_meta;		// The users meta table, jeez

	protected $save_valid;		// Do we have valid user data? Used for saving and loading meta
	protected $save_meta_valid;	// Valid meta data (same as above)

	public $data;			// This is the data array (containing a "meta" array as the last element)

	function __construct($db, $query = false, $load_meta = false, $tb_users = "users", $tb_meta = "users_meta")
	{
		$this->db = $db;
		$this->tb_users = $tb_users;
		$this->tb_meta = $tb_meta;
		$this->save_valid = false;
		$this->save_meta_valid = false;

		if ($id) $this->load($query, $load_meta);
	}

	function __destruct() { }

	// Overloading
	public function __set($name, $value) {
		$this->data[$name] = $value;
	}

	public function __get($name) {
		if (array_key_exists($name, $this->data)) {
		return $this->data[$name];
	}

        $trace = debug_backtrace();
        trigger_error(
            'Undefined property via __get(): ' . $name .
            ' in ' . $trace[0]['file'] .
            ' on line ' . $trace[0]['line'],
            E_USER_NOTICE);
        return null;
 	}

	// I don't think there'll be much trouble here. We just load in a user (w/ or w/out meta). $query can be either a username or a user id.
	function load($query, $load_meta = false)
	{
		if (is_numeric($query))
			$sql = "SELECT * FROM `{$this->tb_users}` WHERE `id` = {$query} LIMIT 1";
		else
			$sql = "SELECT * FROM `{$this->tb_users}` WHERE `username` = {$query} LIMIT 1";

		$rs = mysql_query($sql, $this->db);
		if (!$rs || @mysql_num_rows($rs) == 0) return false;

		$this->clear();

		$row = mysql_fetch_assoc($rs);
		$this->data = $row;

		$this->save_valid = true;
		if ($load_meta) return $this->loadMeta();
		return true;
	}

	// Load the meta for the current user
	function loadMeta()
	{
		if ($this->save_valid)
		{
			$sql = "SELECT * FROM `{$this->tb_meta}` WHERE `id` = {$this->data["id"]} LIMIT 1";
			$rs = mysql_query($sql, $this->db);
			if (!$rs || @mysql_num_rows($rs) == 0) return false;

			$row = mysql_fetch_assoc($rs);
			$this->data["meta"] = $row;
			$this->save_meta_valid = true;

			return true;
		}
		else
			return false;
	}

	function save($save_meta = false)
	{
		if ($this->save_valid)
		{
			$id = $this->data["id"];
			$query = "";
			foreach($this->data as $key => $value)
			{
				if ($key != "meta" && $key != "id")
				{
					if (!is_numeric($value)) $value = "'{$value}'";
					$query .= "`{$key}` = {$value}, ";
				}
			}
			$query = rtrim($query, " ,");
			$sql = "UPDATE `{$this->tb_users}` SET {$query} WHERE `id` = {$id} LIMIT 1";
			$return = mysql_query($sql, $this->db);
			if ($save_meta) return $this->saveMeta();
			else return $return;
		}
		return false;
	}

	function saveMeta()
	{
		if ($this->save_meta_valid)
		{
			$id = $this->data["id"];
			$query = "";
			foreach($this->data["meta"] as $key => $value)
			{
				if ($key != "id")
				{
					if (!is_numeric($value)) $value = "'{$value}'";
					$query .= "`{$key}` = {$value}, ";
				}
			}
			$query = rtrim($query, " ,");
			$sql = "UPDATE `{$this->tb_meta}` SET {$query} WHERE `id` = {$id} LIMIT 1";
			return mysql_query($sql, $this->db);
		}
		return false;
	}

	function create($username, $load_meta = false)
	{
		$sql = "SELECT `id` FROM `{$this->tb_users}` WHERE `username` = '{$username}'";
		$rs = mysql_query($sql, $this->db);
		if (mysql_num_rows($rs) > 0) return false;

		$sql = "INSERT INTO `{$this->tb_users}` (`username`) VALUES ('$username')";
		if (!mysql_query($sql, $this->db)) return false;
		$id = mysql_insert_id($this->db);
		$sql = "INSERT INTO `{$this->tb_meta}` (`id`) VALUES ($id)";
		if (!mysql_query($sql, $this->db)) return false;

		return $this->load($id, $load_meta);
	}

	function validate()
	{
		if (isset($this->data["id"]))
		{
			$this->save_valid = true;
			if (isset($this->data["meta"]))
				$this->save_meta_valid = true;
			else
				$this->save_meta_valid = false;
		}
		else
			$this->save_valid = $this->save_meta_valid = false;
	}

	function clear()
	{
		$this->data = array();
		$this->save_meta_valid = $this->save_valid = false;
	}
}

Don’t worry about the validate() function, it’s just used for checking if a user (and his meta) are valid and setting the appropriate variables. This will be used later on, when I’ll publish the UserSet class, which will be able to hold an array of users and iterate through them.

Alright, I guess that’s it. If you have any questions, or perhaps suggestions about the class and possible improvments, I’ll hear them out in the comments below. Thank you.

About the author

Konstantin Kovshenin

WordPress Core Contributor, ex-Automattician, public speaker and consultant, enjoying life in Moscow. I blog about tech, WordPress and DevOps.

1 comment

  • Alright, I reconsidered the meta data storage concept, cause it's not always easy to remember $user->data["meta"]["metaname"], and yes it sounds kind of strange. I might have put the meta together with the user details, but that'd be awfull if field names crossed eachother, and too tricky to remember which fields are meta and which are not while saving. Considering to use $user->meta_metaname instead (with that meta_ prefix). Anyways, I'll publish the new version later on when I'll finish my UserSet class.