TIP: Always save what you want to post, sometimes forums won’t let you re-post even if it hasn’t actually posted…
So I haven’t used OOP before. And so to learn I thought I would create a website using OOP. Only I’ve found it hard to pin down a concept of OOP I can stick with as it seems that people can make anything an object, even if it doesn’t quite match the concept of an object entirely.
So my question is about how you might design a login system and integrate the user data with classes. My initial idea is to have 3 class types, each an extension of the last, with the first just being to display a users ‘profile’ to other users/guests.
The second class would be then for setting this data, only by the user who owns it, and includes the login/register methods.
The third would be an admin class which would be able to then moderate/edit this data for other users where needed. This would have a method to authenticate admin access (access level, re-confirm user is indeed the user)
class Profile
class User extends Profile
class Admin extends User
But then if the current user is wanting to see someone elses profile, I have to create a new user, and there will be a load of unused information being used in the memory?
if i am user 1, and i do $user->fetchProfile(2), it doesnt make much sense to me…
abstract class User {}
class Admin extends User {}
class RegularUser extends User {}
Here I’m using abstract over an interface, because usually and admin can do all the actions of a regular user, plus more.
Assuming you got the ID of the user’s profile you want to see, say user id 2 then a factory class would work perfect. A factory class instantiates a specific object for you.
$guestId = 2; // pulled from a get /post request
//Users is a factory class
$guest = Users::getById($guestId); //returns a Admin / GeneralUser object
if ($guest != null) {
$profile = $guest->fetchProfile(); // returns a Profile object
//we can't assume every user has a profile so we again have to check for null
if ($profile != null) {
//etc..
}
}
I prefer to use exceptions instead when it comes to this over checking if it’s null. As you can see it can get very messy.
$guestId = 2;
try {
$guest = Users::getById($guestId); //returns a Admin / GeneralUser object
$profile = $guest->fetchProfile(); // returns a Profile object
//continue to use guest and profile as much as you like, if it didn't throw an exception,
// then you are safe to assume that neither of the variables are null.
} catch(UserNotFoundException $e) {
// handle the error for when the user is not found. Possibly render a different template.
} catch(ProfileNotFoundException $e) {
// handle the error for when a profile is not found. Possibly render a different template.
}
Don’t get too crazy with exceptions though. It can get hard to maintain.
It may be best also to consider more granular level of control granting access and actions per user based on permissions rather than roles. Using roles while simple can be quit limiting. The advantages of using permissions is a much granular level of control can be achieved the downside is everything becomes much more complex in the process. If two types of users are only needed though than keeping it simple with an authenticated, unauthenticated and admin are the way to go. What I have been practicing recently that works out very well and is flexible is to decorate models with the information based on the logged in user. That way the model decoration logic can easily change without effecting the rest of the software.
Essentially all models/data objects get decorated with access rights columns that are derived for the given user. That way the logic is sealed off from the rest of the application so that all it needs to do is ask, vs. run additional logic to determine whether an action can be carried out for a current user (authenticated, admin, unauthenticated, etc).
Could also considering making them methods, to lazy load permission info for a model if the logic is slightly complex or requires additional hits to the db.
and… the reason why I am talking about this is because user types are actually the start of thing that really matters – permissions layer. Most of the time roles (user types) should be used to assign a group of permissions not be tightly coupled to privileges – permissions should handle that.
You can still achieve what you want without having to go your way oddz.
Using the same example provided by previous posts:
abstract class User {
protected $userPermission;
public function setPermission(UserPermission $up) {
$this->userPermission = $up;
}
}
class Admin extends User {}
class RegularUser extends User {}
$admin = new Admin();
$admin->setPermission(new AdminWritePermission());
// this would return true because we set a write permission object
if ($admin->hasWritePermission()) {
//etc..
} else {
//etc...
}
$admin2 = new Admin();
$admin2->setPermission(new AdminReadPermission());
// this would return false because we set a read permission object
if ($admin->hasWritePermission()) {
//etc..
} else {
//You don't have write permissions, only read!
}
//etc...
Well I mean the question is always has permission to create, update, read and delete what specifically? In most cases update, delete and read are controlled at the entity level. Where as, create is normally dictated by some type of parent entity. In a highly granular and flexible permissions layer those things are normally derived dynamically based on some type of programmatic logic that is not as simple as checking whether someone is part of a certain group of role. Again though, it all depends on how flexible privileges need to be to achieve the business goals.
My suggestion is to use the interfaces instead of inheritance because the ring diamond problem may occur & interfaces can extend & implemented classes or other interfaces easily respectively.
I agree with the idea of having an abstract User class and extending that.
As for the login part, I here is my abstract login class:
<?php
abstract class Login_Abstract implements Observable {
protected $session; // Session obj
protected $db; // DB obj
protected $username; // user submitted
protected $password; // user submitted
protected $passwordEncrypted; // if using encrypted passwords
protected $userId; // if login worked, this will be the user's id in the db
protected $statusCode; // one of the following:
const LOGIN_OK = 1;
const USERNAME_DOES_NOT_EXIST = 2;
const PASSWORD_DOES_NOT_MATCH = 3;
const NOT_VERIFIED = 4;
protected $observers = array();
public function __construct(Session $session, DB $db)
{
$this->session = $session;
$this->db = $db;
}
/*
this method should be responsible for:
1. setting $username & $password
2. encrypting the password (if needed)
3. comparing the username/pass to values in the db
4. setting $statusCode to the correct number
5. if username/pass match, setting $userId
6. calling $this->notify()
*/
abstract public function attempt($username, $password);
public function ok()
{
return $this->statusCode == 1;
}
// login user - save userId in session
public function login($userIdVarName='userId')
{
$this->session->set($userIdVarName, $this->getUserId());
}
// logout user - delete session
public function logout($userIdVarName='userId')
{
$this->session->delete($userIdVarName);
$this->session->destroy();
}
abstract public function getErrorMessage($type='html');
// observers
public function attach(Observer $obs)
{
$key = get_class($obs);
$this->observers[$key] = $obs;
}
public function detach(Observer $obs)
{
$key = get_class($obs);
unset($this->observers[$key]);
}
public function notify()
{
foreach ($this->observers as $obs) {
$obs->update($this);
}
}
}
You can easily extend this and define an attempt() method, and attach observers to do certain things, i.e.:
$login = new User_AdminLogin($session, $db);
$login->attach( new Login_LoggerDB('AdminLogins') );
$login->attach( new Login_FloodControl('AdminLogins') );
$login->attempt($username, $passwordHashed);
if ($login->ok()) {
$login->login();
View::redirect('index.php');
} else {
View::redirect('login.php?ls='.$login->getStatusCode());
}
I usually don’t map user types to specific classes because it can rapidly lead to a very deep and convoluted inheritance tree. Also, if you use Role Based Access or any similar highly granular system this approach can rapidly get out of hand.
That said, PHP 5.4’s traits and grafts will make this approach much more manageable since each user type can have the traits it needs applied in a set order.
is there a difference if the dependency of a component is expressed by
its self type or if it is expressed by inheritance? For exampe if a
component A depends on a component B then one could write either
trait A {
self: B =>
}
or
trait A extends B
The first variant appeals more to me because the dependency is expressed
“inside” and does not abuse inheritance for that means. Yet, it requires
that a user of component A mixes in component B as well. This makes the
first variant awkward.
Are there any other aspects that should be considered? Any further thoughts?
Carla
Object composition does not and cannot solve the same problems traits solve. Unit testing traits is no harder (easier actually) than dealing with complicated object hives that are the result of object composition.
Read the sourcecode. Prototype uses the prototypical inheritance model of javascript to allow for the Enumerables mix-in to be added to several areas of the code - in particular arrays, element collections and hashes. The code controlling these behaviors is in one spot and adding a method to all the descendants is as simple as modifying the enumerable mix-in class. The methods that enumerable adds to the class become a seemless part of the class and further act upon elements of the class they are mixed into which would otherwise be private*
I’ll not expound on this further. Every programmer I’ve met who swears by object composition has had an incomplete understanding of polymorphism and inheritance rules. An understanding of those rules leads to the understanding of the limits of PHP’s current implementation. Common methods sometimes need to be used by several high level classes. Embedding those methods into a distant common ancestor is inefficient at best and leads to difficult code testing at worst. Better then to implement a multiple inheritance schema which is what traits provide for.
Should be regarded as private. Javascript cannot easily enforce protected/private scoping. Ruby however can, and prototype was developed originally as an extension of the Rails framework of Ruby, such that methods common to the Rails framework would be available under javascript.
Just a few points based on your response and we can leave it at that, to agree on a disagreement.
-Comparison of apples and oranges is never a good thing.
-Javascript != PHP and because of that, things are solved differently.
-Traits leads to linear thinking instead of abstractions
-Object composition doesn’t make unit testing any harder. If it feels “too complicated” than it probably was implemented wrong.
-Object composition, helps decouple objects and promotes polymorphism.
-Inheritance isn’t used with object composition.
-Object composition allows the developer to see code at an interface level, without really needing to look at the implementation. You can easily tell what the dependencies are, much like the simple example I provided above.
How does object composition promote polymorphism? Inheritance/abstraction enables polymorphism because the objects share a common interface. Composition offers no such guarantee.
As for seeing dependencies, you can see exactly what the class is inheriting from in the class definition. I think this point is moot.
There are cases where inheritance is better and cases where composition is better… trying to generalise it is rather pointless.
The implementation of traits and grafts in PHP 5.4 addresses those cases where composition has, historically for the language, been effective (or more so). It’s a case where you use the tools the language gives you.