6

I'm starting to code my project and I have used, in some past unfinished project, two different patterns to manage the design of a user class that will need to manage the following things:

  • The creation of a user
  • The edits to a user
  • The deletion of a user
  • The reading of user data

Despite this, we have also to consider that this user class will be extended by the session class which will just set the focused user id as the id provided by the user who is viewing the pages.

We will also have this class users that will manage instead groups of users.

The 2 options I used are the following (simplified):

Different class for different purpose

- class UserMaker($username, $password, $email);
    function giveBirth(); // create the user

- class UserManager($id);
    function edit($field, $value); // edit a specific user field
    function save(); // save all the edits with a single query
    function setTrusted(); // set that user as trusted
    function setAdmin(); // set that user as admin
    function setBanned(); // ban the specific user

- class UserReader($id);
    function get($field); // Get the value of a single field
    function getAll(); // Get all fields from that user as associative array
    function isAdmin(); // self explanation
    function isRegistered(); // self explanation
    function isBanned(); // self explanation

Single class

- class User($id);
    function static giveBirth($username, $password, $email); // create the user, notice this is static

    function edit($field, $value); // edit a specific user field
    function save(); // save all the edits with a single query

    function setTrusted(); // set that user as trusted
    function setAdmin(); // set that user as admin
    function setBanned(); // ban the specific user

    function get($field); // Get the value of a single field
    function getAll(); // Get all fields from that user as associative array

    function isAdmin(); // self explanation
    function isRegistered(); // self explanation
    function isBanned(); // self explanation

Basically, since the only class that does not accept $id as argument for the __construct() is UserMaker we just set the function giveBirth() as static so we can create the user.

What is the best way to design this pattern? Have you got a third-option which you feel better than these?

hakre
  • 193,403
  • 52
  • 435
  • 836
Shoe
  • 74,840
  • 36
  • 166
  • 272
  • 2
    `giveBirth()` is preferred over `factory()`? – alex Mar 15 '11 at 11:11
  • @alex, are you talking about the function name? – Shoe Mar 15 '11 at 11:11
  • @Charlie Yeah, if it instantiates an object from that class, wouldn't `factory()` be a better choice because its use is more widespread? Although `giveBirth()` does sound cool :) – alex Mar 15 '11 at 11:14
  • @alex, oh yeah. You are right, but don't worry, I use a lot of strange names in my code. Thanks for pointing it out anyway. – Shoe Mar 15 '11 at 11:18
  • what about Create() ? Let it return a reference of itself so you can do User::Create->Edit('email','bla@bla.com')->Edit('username','test')->save(); (or an edit function which accepts an array so you can set all 3 at once :-) ) – DoXicK Mar 15 '11 at 11:21
  • Why should a user know how to save itself? Unless you are keen on using ActiveRecord, saving should be the responsibility of a UserTableGateway. – Gordon Mar 15 '11 at 11:36
  • @Gordon, "Why should a user know how to save itself?" ?? What? – Shoe Mar 15 '11 at 11:47
  • @Charlie You put a save() method into User. That's like putting an eat() method on a banana. Banana's dont eat themselves. They are eaten. Users don't save themselves. They are saved. – Gordon Mar 15 '11 at 11:51
  • Well, if I perform a query every time i make an edit with `edit()` my application would die immediately. Isn't it better to "cache" the edits inside the class and then call a single query with a method such as `save()`? – Shoe Mar 15 '11 at 12:12
  • @Charlie I said nothing about making a query when edit is called. And indeed, that would be wrong to do so. So is putting a save method onto User. Your User object should not have any connections to the database whatsoever (unless you are using ActiveRecord). Make it a plain old php object (POPO). Whenever you need to persist that object into the database, pass it to another object which knows how to save User objects. It's not the responsibility of the User to save itself. – Gordon Mar 15 '11 at 12:36
  • @Gordon, I don't get what's wrong with this. You are just telling me "Don't do that", you are not arguing. The user is just an abstract instance of a row in the database. How should User not be related with Database? – Shoe Mar 15 '11 at 12:40
  • @Charlie But I did argue. I said, it's not the responsibility of the User *unless* it is an ActiveRecord. ActiveRecord *wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data*. This leads to intimate coupling and violates the Single Responsibility Principle. Changes in the database ripple to the object immediately and vice versa. That pattern is only useful if there is no impedance mismatch between your User Object and your User DB Table structure. – Gordon Mar 15 '11 at 12:50
  • @Gordon, now you did. Ok. I'll consider your suggestion. – Shoe Mar 15 '11 at 12:53

2 Answers2

6

Well, the answer to this question relates specifically with The Single Responsibility Principle. Basically, each and every class in your application should have exactly one responsibility. That doesn't mean that it can't be used in more than one situation, but it shouldn't be responsible for more than one abstract concept.

So, from your examples, I would build a class structure similar to this:

class UserFactory() 
    getModel();
    getUser($id, $model = null);
    getACL($user);

class UserModel ()
    edit($id = 0);
    load($id = 0);
    reload($id = 0);
    save($id = 0);

class User ($data)
    getAll();
    getField($field);

class UserACL (User $user)
    isTrustedUser();
    isAdminUser();
    isBannedUser();

That way, everything is organized by responsibility and role, rather than by relation. The benefit to this, is that if you want to swap out an individual component later (for example, the ACL system, or the Model storage layer), you don't need to worry about testing a huge class. Just test that particular API and you're done.

ircmaxell
  • 163,128
  • 34
  • 264
  • 314
  • 1
    What `ACL` stands for? What does `load()` and `reload()` do? How should i create a user? – Shoe Mar 15 '11 at 12:11
  • [Access Control List](http://en.wikipedia.org/wiki/Access_control_list) aka permissions; `load` loads data into the object from the database, not sure what `reload` would do here. You'd create a user by asking for a new blank, object from the factory, populating it with data, then saving it. – Charles Mar 15 '11 at 15:24
  • What is the different between UserFactory and User class? Look similar to me. – I'll-Be-Back Feb 13 '12 at 15:51
0

I do not know how exactly have you organized/conceived stuff in your system although from the approaches that I have used, I've found out that the most efficient way of handling stuff is by conceiving objects as services for example: How many times in your software will the giveBirth method be called? once, twice more ? why more?

Although let's say that your user object must expose methods which can not be called in an exact place every time or you want it to have a more general effect.

  1. Create an interface(or interfaces) with the methods headers.
  2. Create a SINGLETON that implements the interface (all the interfaces). That is because I assume you will never have more then an instance of an user during one request.(fact that is also available for the session).

in the singleton you will have an init static method which can receive an id in order to create the user instance. You will have something like this in the index as I do assume there is a single entry to your application

UserSingleton::init($_SESSION["id"]); 

after the request process initializes the user object you can use this :

$user =  UserSingleton::getInstance(); // will return the instance of singleton

$user->setBanned();// or whatever you need to call;

Obs: you can avoid the init method by creating the getInstanceMethod like this:

 public function getInstance(){
       if(!isset(self::$instance) {
           self::$instance = new UserSingleton($_SESSION["user_id"]) ;
       }
       return self::$instance;
 }

Take note that this must not handle stuff like interaction with the database, this should receive/send data to/from the database through external stuff. It is a helper not a business model/object. That is another concern.

Catalin Marin
  • 1,252
  • 1
  • 11
  • 18
  • -1. [Singletons have no use in PHP](http://gooh.posterous.com/singletons-in-php) and [Statics are Death to Testability](http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/). So are [Globals](http://stackoverflow.com/questions/5166087/php-global-in-functions/5166527#5166527). – Gordon Mar 15 '11 at 11:40
  • No, the user class will be used more than one (not the `giveBirth()` method of course), so Singleton is totally useless. Also could you explain why `this must not handle stuff like interaction with the database`? This of course handles only database interaction, how do you think I'm adding and editing users data? – Shoe Mar 15 '11 at 11:51
  • there is something called MVC and user interaction is handled by the Model, something which does not seem to use. – Catalin Marin Mar 15 '11 at 11:51
  • @Gordon I'll give you an example: you know that the PHP session_start() blocks the access to the certain php file that uses it, result ajax async is useless. What can be done is opening the session, storing it in an object, closing the session write, do your stuff by putting session data insinde that object, start the session again put data from the object into SESSION, close session write again. Now if you tell me how to do that without a singleton. I'll agree with you. I can give you an example of the Multiton use in php. – Catalin Marin Mar 15 '11 at 11:56
  • @Catalin there is absolutely no reason why you need a Singleton for that. Use a regular object. Done. Singleton are not unique snowflakes. A Singleton in one request is still a completely different instance than a Singleton in a simultaneous request. There is no shared application memory in PHP. – Gordon Mar 15 '11 at 12:28
  • @Gordon I am highly aware of the fact that there is no shared memory as PHP lacks runtime environment is a request-live-respond-die thing.That is why I've built my mvc as Services (more like DAO - APP - INTERFACE) linked by registries and joint points. Although you can share stuff between requests by serializing the singleton instance and deserializing it on the first request on the instance, that is how I've built a wizard class once. Singletons can be used in PHP as helpers and only helpers by facilitating access to a certain functionality during one request. – Catalin Marin Mar 15 '11 at 12:52
  • @Catalin Why would you need a Singleton for Helpers? If you want to make sure there is only one instance of an object during that request, simply dont instantiate a second one. If you dont put a `new Foo` anywhere, there won't be a second instance. – Gordon Mar 15 '11 at 13:02