Jump to content


Photo

Using several methods or class constants


  • Please log in to reply
21 replies to this topic

#1 DaveyK

DaveyK

    Advanced Member

  • Members
  • PipPipPip
  • 288 posts
  • LocationThe Netherlands

Posted 16 May 2013 - 06:28 AM

Hey Freakers,

 

I have a question for you. Its about PHP OOP. Basically, I am rewriting my users class and I have a method to fetch info (surprise). However, the amount or kind of info I require is different for some situations. Sometimes I only need basic info (id, email, avatar) and sometimes I need all of it (id, email, full name, avatar and other relevant information). The question here is, how do YOU guys do this. I always used seperate methods:

<?php

class Users
{
  public function fetchInfo ($user_id)
  {
    // fetch user id, full name, email and other relevant info
  }
  
  public function fetchInfoMin ($user_id)
  {
    // fetch a small amount of required info
  }
}

?>

This works fine, but I use cases where the amount of info to be fetched is determined by method variables (often constans) and I wonder how that would work. I imagine something like this:

<?php

class User
{
  const $fetch_all = 'fetch_all';

  public function fetchInfo($user_id, $fetch_all = self::FETCH_ALL)
  {
     // set the sql based on the $fetch_all variable.
  }
}

?>

I wrote that out of the top of my head and frankly I have no idea how that would work, unless just for a lot of if statements changing the SQL and afterwards also the data manipulation on the function itself.

 

How do you guys do this? What is best practise?



#2 trq

trq

    Advanced Member

  • Administrators
  • 30,923 posts
  • LocationSydney, Australia.

Posted 16 May 2013 - 07:04 AM

A User class would generally be modeled on your data, with each database column being represented by a public property or (better still) a getter method.

A fetchInfo() method doesn't really make sense.

http://thorpesystems.com | http://proemframework.org | http://github.com/trq

SmtpCatcher - A very simple mock sendmail useful for testing PHP mail scripts.
OPM - My Linux package manager.


#3 Strider64

Strider64

    Advanced Member

  • Members
  • PipPipPip
  • 119 posts
  • LocationA burb of Detroit, MI

Posted 16 May 2013 - 07:31 AM

It took me a while to understand OOP and I'm always learning something new.  Just remember all you attributes have to correspond to the database table columns:

 

Fore example a User class attributes might look something like:

 

 

protected $id = null;
protected $userType = null;
protected $username = null;
protected $email = null;
protected $pass = null;
protected $dateAdded = null;

You can even have specialized methods(functions)

 

// Method returns a Boolean if the users is an administrator:
function isAdmin() {
return ($this->userType == 'admin');
}

And I think the following will really clear this up (This is part of my login.php file):

// Check against the database:
$query = 'SELECT id, userType, username, email FROM users WHERE email=:email AND pass=SHA1(:pass)';
$stmt = $pdo->prepare($query);
$result = $stmt->execute(array(':email' => $email->getValue(), ':pass' => $password->getValue()));


// Try to fetch the results:
if ($result) {
$stmt->setFetchMode(PDO::FETCH_CLASS, 'User');
$user = $stmt->fetch();
}

 

 

 

 

 

 


A simple basic registration/login tutorial can be found here: http://www.jrpepp.co...en.php?page=PHP


#4 cpd

cpd

    ¬_¬

  • Members
  • PipPipPip
  • 881 posts
  • LocationLondon, UK

Posted 16 May 2013 - 12:09 PM

I'd recommend making use of the magic methods, particularly "__get()" and "__set()" as they're extremely useful for managing your variables when it comes to modeling your database; this essentially builds upon what trq said.


"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it."

"One of my most productive days was throwing away 1000 lines of code."

#5 DaveyK

DaveyK

    Advanced Member

  • Members
  • PipPipPip
  • 288 posts
  • LocationThe Netherlands

Posted 16 May 2013 - 12:46 PM

A User class would generally be modeled on your data, with each database column being represented by a public property or (better still) a getter method.

A fetchInfo() method doesn't really make sense.

 

How doesnt it? I need a function/method to return me the info I need to display the user profile I am currently viewing. In normal php, I would just use a fetch_user_info () function. Would you mind clearifying and showing me how it SHOULD be done rather than critizing how it shouldnt be done.

 

 

It took me a while to understand OOP and I'm always learning something new.  Just remember all you attributes have to correspond to the database table columns:

 

Fore example a User class attributes might look something like:

 

 

protected $id = null;
protected $userType = null;
protected $username = null;
protected $email = null;
protected $pass = null;
protected $dateAdded = null;

You can even have specialized methods(functions)

 

// Method returns a Boolean if the users is an administrator:
function isAdmin() {
return ($this->userType == 'admin');
}

And I think the following will really clear this up (This is part of my login.php file):

// Check against the database:
$query = 'SELECT id, userType, username, email FROM users WHERE email=:email AND pass=SHA1(:pass)';
$stmt = $pdo->prepare($query);
$result = $stmt->execute(array(':email' => $email->getValue(), ':pass' => $password->getValue()));


// Try to fetch the results:
if ($result) {
$stmt->setFetchMode(PDO::FETCH_CLASS, 'User');
$user = $stmt->fetch();
}

 

I dont really understand the advantage of this. How do you set the user id then. and how do you get the info out of the method if you dont return it?! I dont know OOP and its all just me trying to do some stuff without anyone helping me. Ive never been schooled in PHP OOP so forgive me for not understanding this.

 

EDIT: I replied inside the quote. Not too bright.


Edited by DaveyK, 16 May 2013 - 12:47 PM.


#6 cpd

cpd

    ¬_¬

  • Members
  • PipPipPip
  • 881 posts
  • LocationLondon, UK

Posted 16 May 2013 - 01:11 PM

You're getting confused between your data model layer and your database layer; arguably one-in-the-same. 

 

The logic for interacting with the database, i.e. the logic to actually run a query, should ideally be in a class of its own; this acts as a portal for all queries. A data model class wraps, normally, a single row from your query into an object. You can then pass this object between other objects to keep the information contained. If you want to pass multiple rows you can create an iterator and pass the iterator around.

 

Or perhaps have an iterator contained within the data model that, when iterated through, sets the fields to their appropriate values within the data model.


Edited by cpd, 16 May 2013 - 01:13 PM.

"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it."

"One of my most productive days was throwing away 1000 lines of code."

#7 ignace

ignace

    Now mod flavored

  • Moderators
  • 6,239 posts
  • LocationBelgium

Posted 16 May 2013 - 01:38 PM

Strider64, on 16 May 2013 - 2:31 PM, said:
 

// Check against the database:
$query = 'SELECT id, userType, username, email FROM users WHERE email=:email AND pass=SHA1(:pass)';
$stmt = $pdo->prepare($query);
$result = $stmt->execute(array(':email' => $email->getValue(), ':pass' => $password->getValue()));


// Try to fetch the results:
if ($result) {
$stmt->setFetchMode(PDO::FETCH_CLASS, 'User');
$user = $stmt->fetch();
}

This demonstrates the apparent problem you face when you use OOP and an RDBMS without an ORM. This code will work until you have an object with relations to other objects. Suppose User has a Role object. So you would need 2 queries or a JOIN but then you will have to manually write out the colums you need, possibly aliasing a few and then mapping them to the appropriate objects. This becomes really tedious real soon which is why everyone uses an ORM. The above code also assumes all your properties are public which is not good.

For PHP there are multiple ORM's available the most popular being Doctrine and Propel.

Edited by ignace, 16 May 2013 - 01:40 PM.


#8 ignace

ignace

    Now mod flavored

  • Moderators
  • 6,239 posts
  • LocationBelgium

Posted 16 May 2013 - 01:45 PM

How doesnt it? I need a function/method to return me the info I need to display the user profile I am currently viewing. In normal php, I would just use a fetch_user_info () function. Would you mind clearifying and showing me how it SHOULD be done rather than critizing how it shouldnt be done.


$user->fetchInfo(1);
What info? Addresses? Girlfriends? And where is it getting it from? Your User object shouldnt be aware of a database, like so:
 
class User {
  private $id;
  private $email;
  private $pass;
  
  public function getId() { .. }
  public function getEmail() { .. }
}
To get this User from the database I would do:
 
$user = $em->find('User', 1);
print $user->getEmail();
To change his e-mail address and store it into the database:
 
$user->setEmail('my-brand-spanking-new@gmail.com');
$em->persist($user); // assuming explicit strategy
$em->flush();
The advantage to this way of programming is that I can use a User object in other parts of my application without needing a database:
 
$from = new User();
$from->setEmail('bert@bert-en-ernie.nl');

$to = new User();
$to->setEmail('ernie@bert-en-ernie.nl');

$messageSender->sendMessage($from, $to, 'Je hebt een banaan in je oor!');

Edited by ignace, 16 May 2013 - 01:53 PM.


#9 DaveyK

DaveyK

    Advanced Member

  • Members
  • PipPipPip
  • 288 posts
  • LocationThe Netherlands

Posted 16 May 2013 - 02:17 PM

This demonstrates the apparent problem you face when you use OOP and an RDBMS without an ORM. This code will work until you have an object with relations to other objects. Suppose User has a Role object. So you would need 2 queries or a JOIN but then you will have to manually write out the colums you need, possibly aliasing a few and then mapping them to the appropriate objects. This becomes really tedious real soon which is why everyone uses an ORM. The above code also assumes all your properties are public which is not good.

For PHP there are multiple ORM's available the most popular being Doctrine and Propel.

 

I see. I had to google search a bit but I think I get it. At this point I manually write all queries (and columns) I need in seperate functions/methods. I just dont know any better.

 

$user->fetchInfo(1);
What info? Addresses? Girlfriends? And where is it getting it from? Your User object shouldnt be aware of a database, like so:
 
class User {
  private $id;
  private $email;
  private $pass;
  
  public function getId() { .. }
  public function getEmail() { .. }
}
To get this User from the database I would do:
 
$user = $em->find('User', 1);
print $user->getEmail();
To change his e-mail address and store it into the database:
 
$user->setEmail('my-brand-spanking-new@gmail.com');
$em->persist($user); // assuming explicit strategy
$em->flush();
The advantage to this way of programming is that I can use a User object in other parts of my application without needing a database:
 
$from = new User();
$from->setEmail('bert@bert-en-ernie.nl');

$to = new User();
$to->setEmail('ernie@bert-en-ernie.nl');

$messageSender->sendMessage($from, $to, 'Je hebt een banaan in je oor!');

 

Regarding the fetchInfo() method question: Yes, information. If that were a function, I would make it return all information, something like: id, user name, email, status, avatar and possibly more info based on the users data. I just simply write an sql query and return the data.

 

Regarding the rest. I am terribly sorry but I just dont follow. First:

$user = $em->find('User', 1);
print $user->getEmail();

what is $em ?! I think you are assuming I know what this is, while in face I dont have the slighest clue.

$from = new User();
$from->setEmail('bert@bert-en-ernie.nl');

$to = new User();
$to->setEmail('ernie@bert-en-ernie.nl');

$messageSender->sendMessage($from, $to, 'Je hebt een banaan in je oor!');

Again, I dont understand the use of this. First of all, no one likes a banana in ones ear.

Second, you already have the $from variable, so why would you add it to a class and then fetch it from the same class. What am I missing here?!

Also, "without needing a database". Do $from and $to just magically appear? Unless you fetch from a form (get, post) or from a session or something, that information is already likely fetched from a database.

 

 

here is an example of my User.class.php file.

class User
{
  private $logged_in_user_id;
  
  public function __construct ($user_id = false)
  {
    $this->logged_in_user_id = $user_id;
  }
  
  public function fetchInfo ($user_id = false)
  {
    if ($user_id === false)
    {
      $user_id = $this->logged_in_user_id;
    }
	
	global $pdo;
    
	$user_id = intval($user_id);
	
	$statement = $pdo->prepare("SELECT
                `id`,
                `email`,
                `status`,
                `password`,
                `salt`
              FROM `users`
              WHERE `id` = ?");
	
	$statement->execute(array($user_id));
	
	return $statement->fetch(PDO::FETCH_ASSOC);
  }
  
  public function setLoggedInUser ($user_id)
  {
    $this->logged_in_user_id = $user_id;
  }
}

I assume that its about 100% between "class User {" and "}" is wrong, but please keep in mind that im just doing what I think works and I am coming here because I want to improve my code and expand my knowledge, so please help me...


Edited by DaveyK, 16 May 2013 - 02:20 PM.


#10 cpd

cpd

    ¬_¬

  • Members
  • PipPipPip
  • 881 posts
  • LocationLondon, UK

Posted 16 May 2013 - 03:45 PM

From what you've said it sounds as though you'd merely return an array of data detailing the user information. A good object oriented design involves encapsulating data in the correct data structures with the correct visibility. For example the ID field of a user could be considered a property that shouldn't be overwritten and therefore you only provide a getter for that attribute. In contrast you could have the first and last name of a user that you want to be able to overwrite so you can write a getter and setter.

 

You can then appropriately manipulate the state of the object and commit it to the database via the ORM.


Edited by cpd, 16 May 2013 - 03:45 PM.

"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it."

"One of my most productive days was throwing away 1000 lines of code."

#11 kicken

kicken

    Wiser? Not exactly.

  • Gurus
  • 2,651 posts
  • LocationBonita, FL

Posted 16 May 2013 - 11:39 PM

what is $em ?! I think you are assuming I know what this is, while in face I dont have the slighest clue.

The $em variable would hold an instance of something known as an Entity Manager. That is basically a class that knows about your different entities (Users, Messages, Blogs, Photos, whatever) and handles the creation of those entities. Something like $em->find('User', 1) would be asking the entity manager that you want it to find the User entity with ID #1 so what it would do is generate the appropriate query to pull the information from the database, create a new instance of the User class, and then set all the properties of that class with the values from the database. How it knows what to do generally comes down to two possibilities:
A) Everything follows a strict naming standard. Your class names match up with your table names. Your property names match up with your column names. And so on for any other cases
B) or everything is configured via some configuration file which defines which entities your application has, what class is used to represent them, which properties match up with with columns, etc.

The second approach is more flexable generally but also more work in the initial setup. Often there is a script you can use that will apply the principals of method a to automatically generate a configuration for method b which can then be tweaked if necessary.

 

Second, you already have the $from variable, so why would you add it to a class and then fetch it from the same class. What am I missing here?!

The point is about having consistant interfaces and being able to use them easily. In the example above, there is some class that is used for sending email messages between users. It accepts two User objects and the message as it's parameters and then acquires the user's email (and potentially other details, ie user ID, names, etc) from those user objects by calling the appropriate properties (ie, $from->getEmail, $from->getName, etc). By having your code always deal with user objects you then always have a nice consistant way to access a users data.

 

Also, "without needing a database". Do $from and $to just magically appear? Unless you fetch from a form (get, post) or from a session or something, that information is already likely fetched from a database.

One prime example of not needing a database would be if you do Unit Testing. In such an environment you would create "fake" data and use that to do the test. So in the example you'd create two User objects with fake details and pass that to your messaging object in order to test that it is working properly. Rather than go through a huge hassle of setting up a DB and anything else that might be neccessary for that to work you just create two simple objects, set their details, and go.


Now, all of that said/explained:

How do you guys do this?

Our application is similar to what you have said where we have methods that return an array of all the various information for a given entity. For the most part I've gone the route of using a single method, GetDetails, with parameters to control which information to load. For example we have a Student class that returns details about a student. Whether it loads additional details about their enrollments though is a parameter. The method is defined as

public static function GetDetails($studentId, $loadEnrollments=false);


When all we need is things like their name, email, etc then we skip loading the enrollments. If we need the enrollment data then we just set the second parameter to true in order to have it pull that information as well. As far as which is best, separate methods vs parameters, it doesn't really matter. Either will work fine, neither is really fantastic. Something more advance like describe in previous posts would likely be best just from a maintainability/testability point of view, which is the most important point of view.
Recycle your old CD's, don't trash them!
Did I help you out?  Feeling generous? I accept tips via Paypal or Bitcoin @ 14mDxaob8Jgdg52scDbvf3uaeR61tB2yC7

#12 DaveyK

DaveyK

    Advanced Member

  • Members
  • PipPipPip
  • 288 posts
  • LocationThe Netherlands

Posted 17 May 2013 - 01:41 AM

Okay, I can see that this might be advantageous if you implement it from the beginning, but I am writing an admin panel to a site and I only just started using PDO sql where the normal site uses MySQL. Implementing an ORM like doctrine or propel is a step to far for me. I am only just getting used to PDO and having to learn that ORM as well will set me back way too long.

 

The thing I find interesting in your post tho is that $loadEnrollments=false. What do you do with that variable to alter the sql? This was my original question.



#13 kicken

kicken

    Wiser? Not exactly.

  • Gurus
  • 2,651 posts
  • LocationBonita, FL

Posted 17 May 2013 - 01:11 PM

The thing I find interesting in your post tho is that $loadEnrollments=false. What do you do with that variable to alter the sql? This was my original question.

In my particular case it just does a second query and then merges the results together in the code. Ie:
public static function GetDetails($id, $loadEnrollments=false){
   $stmt = /* query to get basic details */;
   $details = $stmt->fetchAll();
   if ($loadEnrollments){
       $stmt = /* query to get enrollment data */
       $details['enrollments'] = $stmt->fetchAll();
   }

   return $details;
}
Depending on your needs though you could do anything from just change an array of fields to be selected to simply using two different querys.
$fields = array('a','b','c');
if ($loadEnrollments){ 
   $fields=array_merge($fields, array('d','e','f'));
}

//or
if ($loadEnrollments){
  $sql = 'The big query';
}
else {
  $sql = 'The smaller query';
}

$stmt = $db->query($sql);

Recycle your old CD's, don't trash them!
Did I help you out?  Feeling generous? I accept tips via Paypal or Bitcoin @ 14mDxaob8Jgdg52scDbvf3uaeR61tB2yC7

#14 Hall of Famer

Hall of Famer

    OOP Fanboi

  • Members
  • PipPipPip
  • 315 posts
  • LocationIthaca

Posted 17 May 2013 - 04:57 PM

 

It took me a while to understand OOP and I'm always learning something new.  Just remember all you attributes have to correspond to the database table columns:

 

Fore example a User class attributes might look something like:

 

 

protected $id = null;
protected $userType = null;
protected $username = null;
protected $email = null;
protected $pass = null;
protected $dateAdded = null;

You can even have specialized methods(functions)

 

// Method returns a Boolean if the users is an administrator:
function isAdmin() {
return ($this->userType == 'admin');
}

And I think the following will really clear this up (This is part of my login.php file):

// Check against the database:
$query = 'SELECT id, userType, username, email FROM users WHERE email=:email AND pass=SHA1(:pass)';
$stmt = $pdo->prepare($query);
$result = $stmt->execute(array(':email' => $email->getValue(), ':pass' => $password->getValue()));


// Try to fetch the results:
if ($result) {
$stmt->setFetchMode(PDO::FETCH_CLASS, 'User');
$user = $stmt->fetch();
}

 

 

 

 

 

Try not to use transaction script unless you do not have a choice, instead apply the data mapper design pattern to make the best use of OOP. Transaction script is not even OOP to begin with, it is an anti-pattern with objects written in procedural code. 


Welcome to the world of OOPHP! In a perfect script, everything is an object. You cannot be perfect, but you can approach as close as can.

zog841.jpg


#15 ignace

ignace

    Now mod flavored

  • Moderators
  • 6,239 posts
  • LocationBelgium

Posted 18 May 2013 - 02:20 AM

In my particular case it just does a second query and then merges the results together in the code. Ie:

public static function GetDetails($id, $loadEnrollments=false){
   $stmt = /* query to get basic details */;
   $details = $stmt->fetchAll();
   if ($loadEnrollments){
       $stmt = /* query to get enrollment data */
       $details['enrollments'] = $stmt->fetchAll();
   }

   return $details;
}
Depending on your needs though you could do anything from just change an array of fields to be selected to simply using two different querys.
$fields = array('a','b','c');
if ($loadEnrollments){ 
   $fields=array_merge($fields, array('d','e','f'));
}

//or
if ($loadEnrollments){
  $sql = 'The big query';
}
else {
  $sql = 'The smaller query';
}

$stmt = $db->query($sql);


If data transfer objects is what you are looking for. Doctrine has you covered aswell:
http://docs.doctrine...operator-syntax

#16 ignace

ignace

    Now mod flavored

  • Moderators
  • 6,239 posts
  • LocationBelgium

Posted 18 May 2013 - 02:34 AM

Try not to use transaction script unless you do not have a choice, instead apply the data mapper design pattern to make the best use of OOP. Transaction script is not even OOP to begin with, it is an anti-pattern with objects written in procedural code.


:facepalm:

If you don't take it from me, read this: http://stackoverflow.com/a/16400738

Edited by ignace, 18 May 2013 - 02:37 AM.


#17 DaveyK

DaveyK

    Advanced Member

  • Members
  • PipPipPip
  • 288 posts
  • LocationThe Netherlands

Posted 18 May 2013 - 05:38 AM

In my particular case it just does a second query and then merges the results together in the code. Ie:

public static function GetDetails($id, $loadEnrollments=false){
   $stmt = /* query to get basic details */;
   $details = $stmt->fetchAll();
   if ($loadEnrollments){
       $stmt = /* query to get enrollment data */
       $details['enrollments'] = $stmt->fetchAll();
   }

   return $details;
}
Depending on your needs though you could do anything from just change an array of fields to be selected to simply using two different querys.
$fields = array('a','b','c');
if ($loadEnrollments){ 
   $fields=array_merge($fields, array('d','e','f'));
}

//or
if ($loadEnrollments){
  $sql = 'The big query';
}
else {
  $sql = 'The smaller query';
}

$stmt = $db->query($sql);

I'm just gonna use this since it makes most sense to me. I understand that using an ORM could be better but I simply don't have the time to learn that right now. It would take way too long to implement.

Thanks for the help guys

#18 Hall of Famer

Hall of Famer

    OOP Fanboi

  • Members
  • PipPipPip
  • 315 posts
  • LocationIthaca

Posted 19 May 2013 - 02:06 AM

:facepalm:

If you don't take it from me, read this: http://stackoverflow.com/a/16400738

 

Well transaction script uses objects but is truly procedural code in disguise so its considered a poor OOP practice.


Welcome to the world of OOPHP! In a perfect script, everything is an object. You cannot be perfect, but you can approach as close as can.

zog841.jpg


#19 ignace

ignace

    Now mod flavored

  • Moderators
  • 6,239 posts
  • LocationBelgium

Posted 19 May 2013 - 02:13 AM

so its considered a poor OOP practice.


Your opinion does not resemble the general consensus.

#20 trq

trq

    Advanced Member

  • Administrators
  • 30,923 posts
  • LocationSydney, Australia.

Posted 19 May 2013 - 05:57 AM

Your opinion does not resemble the general consensus.


It rarely does.

http://thorpesystems.com | http://proemframework.org | http://github.com/trq

SmtpCatcher - A very simple mock sendmail useful for testing PHP mail scripts.
OPM - My Linux package manager.





0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

Cheap Linux VPS from $5
SSD Storage, 30 day Guarantee
1 TB of BW, 100% Network Uptime

AlphaBit.com