SitePoint Sponsor

User Tag List

Results 1 to 23 of 23
  1. #1
    SitePoint Enthusiast aivarannamaa's Avatar
    Join Date
    Sep 2002
    Location
    Estonia
    Posts
    36
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    How to pass around User object

    It's about access control: domain object wants to know about current user in order to decide if it may execute some action on behalf of this user or should it deny that action. Or it may just choose slightly different action depending on user.

    One possibility would be to give user to domain object constructor. But in some environments user may change during domain object lifetime.

    Other possibility would be that domain object require user as method parameter when it needs it. This seems clumsy.

    Yet another possibility would be to store user as a global object (or set up a possibility to get it as a singleton). Yes, globals are evil, but doesn't this count as a special case?

    Anyway, it would be great if someone told how he/she has solved or would solve similar problem.

    Aivar
    [Punk on lahe!]

  2. #2
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well, I really cheat and have a "stealth global".

    PHP Code:
    define('USER_SESSION_KEY','_user_data');
    define('USER_ID_KEY','user_id');

    class 
    User {
      var 
    $data;
      function 
    User() {
      if (!(
    array_key_exists(USER_SESSION_KEY$_SESSION)
        && 
    is_array($_SESSION[USER_SESSION_KEY])) {
        
    $_SESSION[USER_SESSION_KEY] = array();
      }
      
    $this->data =& $_SESSION[USER_SESSION_KEY]

    Now, anywhere in the class, $this->data is actually bound to the php superglobal array _SESSION at the index defined by USER_SESSION_KEY and therefore anything you tuck into the associative array (like the currently authenticated user, or that users permissions) will be accessible to _ANY_ instance of User that you create.

    This was a technique I developed prior to using Test Driven Development
    Jason Sweat ZCE - jsweat_php@yahoo.com
    Book: PHP Patterns
    Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
    Detestable (adjective): software that isn't testable.

  3. #3
    Non-Member
    Join Date
    Jan 2004
    Location
    Planet Earth
    Posts
    1,764
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Canl you elaborate a bit more about this method you have SweatJe.

    Such as describing what you mean by the superglobal array _SESSION ?

    ie

    PHP Code:
    // at start of file
    $_SESSION['_SESSION'] ...

    // or

    global $_SESSION ... 
    I like the idea and would like to know more

  4. #4
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Widow Maker
    Such as describing what you mean by the superglobal array _SESSION ?
    PHP Code:
    // at start of file
    $_SESSION['_SESSION'] ...

    // or

    global $_SESSION ... 
    $_SESSION is a php superglobal meaning that you can use it anywhere without having to declare it as a global.

    The only thing you would have to remember is to always
    PHP Code:
    session_start(); 
    before any
    PHP Code:
    $user =& new User

  5. #5
    Non-Member
    Join Date
    Jan 2004
    Location
    Planet Earth
    Posts
    1,764
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks I'm going to bookmark this thread..

  6. #6
    SitePoint Evangelist ghurtado's Avatar
    Join Date
    Sep 2003
    Location
    Wixom, Michigan
    Posts
    591
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Jason, unless I'm mistaken, what Aivar is looking for is for a way to take that "User" object you showed us, which would probably contain other auth checks (IE: isUserAdmin, isUserManager...) and then make use of it from the domain object, which could be some other object (IE: newsModel - to indulge the eternal example )

    Personally, since you are dealing with Authorization/Authentication, I think that a singleton / registry / service approach would be the cleanest. Your model code might look like:
    PHP Code:
    class NewsModel(){
        function 
    NewsModel(){
            
    $app =& ApplicationServer::getInstance();
            
    $this->authenticator =& $app->getService('auth');
        }
        
        function 
    getNews($date){
            if(
    $this->authenticator->isAllowed('getNews')){
                
    // get news ...
                
    return $news;
            }
            else {
                
    trigger_error('You are not authorized to view this page');
            }
        }

    By having a sort of "services manager" or application server, if you will, this can act as the auth object factory, and thus allow your domain objects to acquire auth information from any number of auth validation classes.

    What do you think about this approach?

  7. #7
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    The service layer approach is much cleaner from an OOP perspecive . Everyone should have been on the lookout when I mentioned "stealth global" While it is functional, and takes advantage of some nice PHP features, it is a bit of a pain to test.

    That being said, if you wanted to use the concept further, you might have some methods like:
    PHP Code:
      function Login($username$passwd) {
        
    //some db code to check against a table
        
    if () { //db stuff returned a row
          
    $this->data[USER_ID_KEY] = $row['user_id'];
        }
      }

      function 
    isLoggedIn() {
        return (
    array_key_exists(USER_ID_KEY$this->data)
          && 
    is_numeric($this->data[USER_ID_KEY])) 
        ? 
    true false;
      } 
    so your "login" form would post to a script that would try:
    PHP Code:
    $user =& new User;
    $user->Login($_POST['user_name'], $_POST['passwd']); 
    and later, perhaps in some other page
    PHP Code:
    $user =& new User;
    if (
    $user->isLoggedIn) {
      
    //authenticated user stuff
    } else {
      
    //anonymous user stuff


  8. #8
    ********* Victim lastcraft's Avatar
    Join Date
    Apr 2003
    Location
    London
    Posts
    2,423
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Hi.

    My feeling is that the problem should be broken down a bit more. One of the user's properties is atht it is an "entity". That is, it has a unique identity and so you cannot freely copy it when passing it around. If all you want is a bunch of permissions passed around then do this...
    PHP Code:
    $permissions $user->getPermissions
    The $permissions object is a constant value object. You can copy it around at will. This should make your problem much less tangled.

    I have said a few times that I think a 'User' class is a bad idea, probably to the point of annoying everbody in site , but I think that this illustrates the point again. You have a session (which clearly is an application scope single entity) and a login (a persistent domain entity) which comes from the authenticator (a stateless factory domain service). The Login can ask the Authorisor (a stateless domain service) for a PermissionSet (a domain value object) for the application to read. It sounds more compicated, but it is actually simpler. The classes are far smaller, more cohesive and far less entangled.

    Basically entities are complicated things. You have to persist them, prevent them being copied, synchronise them and back them up. Value objects are simpler (copyable) lightweight objects and usually constant once created. Services are stateless, they are just code. If you can make the entities as small as possible and push excess information into other places, it should get easier to handle.

    Or it might not. It all depends on the application .

    yours, Marcus
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things

  9. #9
    SitePoint Evangelist ghurtado's Avatar
    Join Date
    Sep 2003
    Location
    Wixom, Michigan
    Posts
    591
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    The way you describe it is almost exactly how we currently approach the problem of security at my work. On the other hand, the way I propose above is more of a "ideal situation" scenario, and I wish I had the time to code it like that. In a perfect world that we might never, ever live to see, cross-cutting concerns such as this one are handled in a "aspect oriented" fashion. PHP doesn't have any AOP capabilities, so the closest we can get is the decorator pattern. Ideally, a model factory could return either the "naked" model, dumb to any cross-cutting aspects such as authorization, or a "decorated" one depending on requirements (in this case, one that knows when to request authorization checks). The problem is that, AFAIK, the decorator can only be implemented in PHP via inheritance, and for an unknown number of domain objects (N) you would need at least N decorator classes. That does not appeal to me, I want to code my domain model class once and only once, all other functionality that is common should be inherited.

    Does anyone have any suggestions in how to approach this? (assuming I just made any kind of intelligible sense )

  10. #10
    ********* Victim lastcraft's Avatar
    Join Date
    Apr 2003
    Location
    London
    Posts
    2,423
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Hi...

    Quote Originally Posted by ghurtado
    The problem is that, AFAIK, the decorator can only be implemented in PHP via inheritance, and for an unknown number of domain objects (N) you would need at least N decorator classes.
    I might have a way around this . I am a long way from my home machine right now, but will post something tomorrow.

    yours, Marcus
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things

  11. #11
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Off the top of my head, I think you can:
    PHP Code:
    class AbstractFoo
    {
      function 
    bar() {}
    }

    class 
    FooDecorator
    {
      var 
    $foo;
      function 
    FooDecorator(&$foo) {
        
    $this->foo =& $foo;
      }
      function 
    bar() {
        
    $this->foo->bar();
      }
      function 
    blah() {
        
    //this is the decorated method
      
    }

    should work for all ConcreteFoo's (i.e. all that implement Bar,and only Bar, publically).

    HTH

  12. #12
    SitePoint Zealot
    Join Date
    Aug 2003
    Location
    Sydney
    Posts
    187
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I have said a few times that I think a 'User' class is a bad idea
    I do believe that a User class is valid, for User related stuff, like email etc, but I do feel that Permissions do belong to another class, most like authorisation, as it's authorising whether or not the user is allowed to do something.

    But then again it could belong to its own class.

    Any layout code for this lastcraft?

  13. #13
    SitePoint Evangelist ghurtado's Avatar
    Join Date
    Sep 2003
    Location
    Wixom, Michigan
    Posts
    591
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Jason, I like your approach, and unless lastcraft shows us some of his magic (which he has certainlydone before ), it's likely the only way to implement this decorator in particular. The only complaint that I have about it is that as the interface of the base class changes (AbstractFoo), then you will have to manually change the corresponding methods in the decorator as well. I wish there was a way around this, it sounds like PHP 5 _call() is exactly what is needed in this case, isnt it? Man, I can't wait to start using the new PHP5 features... my head says "wait until its stable", but my heart longs for it more than is healthy....

  14. #14
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by ghurtado
    unless lastcraft shows us some of his magic (which he has certainlydone before
    I don't recall where I saw this construct first, so it is entirely likely to be Marcus magic anyway

  15. #15
    ********* Victim lastcraft's Avatar
    Join Date
    Apr 2003
    Location
    London
    Posts
    2,423
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Hi...

    Methinks people's confidence in me is about to take a bit of a pasting . What follows is anything but magic. It is brute force eval() and doesn't entirely work anyway.

    The idea is that decorators can be generated from the reflection API. This is rather limited in PHP4 and I don't yet have a working PHP5 box so I am rather restricted here. As a result parameters are not passed by reference without some extra work.

    Usage is...
    PHP Code:
    Decorator::generate('MyClass');
    class 
    MySpecificDecorator extends MyClassDecorator {
        function 
    MySpecificDecorator(&$wrapped) {
            
    $this->MyClassDecorator($wrapped);
        }
        function 
    theOnlyOneToChange($a) {
            ...
            
    $result = &parent::theOnlyOneToChange($a);
            ...
            return 
    $result;
        }

    The idea is that you only have to override the methods you want to change, the other methods are chained automatically by the do-nothing generated decorator. You have to also explicitely chain those where you must pass references, but for simple classes it could ease the drudge.

    To fix the reference issue in the generator, the only thing needed is the parameter count of the method. In PHP4 this means creating some kind of reflection API by parsing the PHP through the tokeniser. If the code is generated from some kind of make file, this might not be so bad.

    Anyway, the code...
    PHP Code:
    class Decorator {
        
        function 
    generate($class$decorator false) {
            if (! 
    $decorator) {
                
    $decorator $class 'Decorator';
            }
            if (
    class_exists($decorator)) {
                return 
    false;
            }
            eval(
    Decorator::generateCode($class$decorator));
            return 
    true;
        }
        
        function 
    generateCode($class$decorator) {
            
    $code  "class $decorator {\n";
            
    $code .= "    function $decorator(&\$wrapped) {\n";
            
    $code .= "        \$this->_wrapped = &\$wrapped;\n";
            
    $code .= "    }\n";
            foreach (
    get_class_methods($class) as $method) {
                
    $code .= "    function &$method() {\n";
                
    $code .= "        \$parameters = func_get_args();\n";
                
    $code .= "        return Decorator::_invoke(\$this->_wrapped, '$method', \$parameters);\n";
                
    $code .= "    }\n";
            }
            
    $code .= "    function &getDecorated() {\n";
            
    $code .= "        return \$this->_wrapped;\n";
            
    $code .= "    }\n";
            
    $code .= "}\n";
            return 
    $code;
        }
        
        function &
    _invoke(&$object$method, &$parameters) {
            
    $aliases = array();
            for (
    $i 0$i count($parameters); $i++) {
                
    $aliases[] = "\$parameters[$i]";
            }
            
    $code '$result = &$object->' $method '(' implode(', '$aliases) . ');';
            eval(
    $code);
            return 
    $result;
        }

    And also a test case for hacking...
    PHP Code:
    if (! defined('SIMPLE_TEST')) {
        
    define('SIMPLE_TEST''simpletest/');
    }
    require_once(
    SIMPLE_TEST 'unit_tester.php');
    require_once(
    SIMPLE_TEST 'mock_objects.php');
    require_once(
    SIMPLE_TEST 'reporter.php');
    require_once(
    'decorator.php');

    class 
    Dummy {
        function 
    Dummy() {
        }
        function 
    a() {
        }
    }
    Decorator::generate('Dummy');

    Mock::generate('Dummy');

    class 
    TestOfDecorator extends UnitTestCase {
        function 
    TestOfDecorator() {
            
    $this->UnitTestCase('Decorator');
        }
        function 
    testSimpleCallChaining() {
            
    $wrapped = &new MockDummy($this);
            
    $wrapped->setReturnValue('a''Out');
            
    $wrapped->expectOnce('a');
            
    $decorator = &new DummyDecorator($wrapped);
            
    $this->assertEqual($decorator->a(), 'Out');
            
    $wrapped->tally();
        }
        function 
    testCallWithParameters() {
            
    $wrapped = &new MockDummy($this);
            
    $wrapped->setReturnValue('a''Out');
            
    $wrapped->expectOnce('a', array(123));
            
    $decorator = &new DummyDecorator($wrapped);
            
    $this->assertEqual($decorator->a(123), 'Out');
            
    $wrapped->tally();
        }
        function 
    testReturnByReference() {
            
    $thing = &new Dummy();
            
    $wrapped = &new MockDummy($this);
            
    $wrapped->setReturnReference('a'$thing);
            
    $decorator = &new DummyDecorator($wrapped);
            
    $this->assertReference($decorator->a(), $thing);
        }
        function 
    testAccessToDecoratedObject() {
            
    $wrapped = &new Dummy();
            
    $decorator = &new DummyDecorator($wrapped);
            
    $this->assertReference($decorator->getDecorated(), $wrapped);
        }
    }

    $test = &new TestOfDecorator();
    $test->run(new HtmlReporter()); 
    yours, Marcus
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things

  16. #16
    SitePoint Zealot
    Join Date
    Aug 2003
    Location
    Sydney
    Posts
    187
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Very interesting. Reading over this, GoF Decorator and http://www.phppatterns.com/index.php...leview/92/1/1/

    By the way Marcus, just got the latest phparch and haven't had time to read, but will do. It will give me more insight into your simple test.

  17. #17
    SitePoint Enthusiast aivarannamaa's Avatar
    Join Date
    Sep 2002
    Location
    Estonia
    Posts
    36
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thank you guys for so much responses.

    I got more confidence for setting up User as a global. (Anyway, what the heck, I'm using global database connection so I'm already "at the slippery road to hell" as someone once said, nothing to lose anymore)

    There were arguments against putting Permissions into User. I somewhat agree with that. I intend my Users to have a "role" attribute (or maybe list of roles) and each domain object decides what to permit for a role.

    Aivar

  18. #18
    ********* Victim lastcraft's Avatar
    Join Date
    Apr 2003
    Location
    London
    Posts
    2,423
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Hi...

    Quote Originally Posted by aivarannamaa
    There were arguments against putting Permissions into User. I somewhat agree with that. I intend my Users to have a "role" attribute (or maybe list of roles) and each domain object decides what to permit for a role.
    You can hide the whole mechanism with...
    PHP Code:
    $authoriser = &new Authoriser();
    $permissions = &authoriser->getPermissions($user); 
    Then if you switch from role base to access list based systems you won't have to change any code.

    yours, Marcus
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things

  19. #19
    SitePoint Zealot
    Join Date
    Feb 2003
    Location
    Virginia
    Posts
    143
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Very interesting code Marcus!

    Just output that to a save directory and update the classes on the decorator file time change.

    Would this be what you had in mind? What other things are needed that you see to expand on this idea?

    Res

  20. #20
    SitePoint Evangelist ghurtado's Avatar
    Join Date
    Sep 2003
    Location
    Wixom, Michigan
    Posts
    591
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Marcus, that is definitely a very interesting approach I hadn't though of. While not the cleanest way to do it, I think I might take it home tonight and try it for myself when I have a bit more time, I have a few modifications in mind .. should be interesting

    I also want to try Jason's approach, some of these examples are hard for me to wrap my head around unless I implement them in a live server.

    Kudos to both of you for what you bring to these boards every single day! I feel fortunate that you are sharing it with all of us

  21. #21
    ********* Victim lastcraft's Avatar
    Join Date
    Apr 2003
    Location
    London
    Posts
    2,423
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Hi...

    Quote Originally Posted by Resolution
    Would this be what you had in mind?
    Yes, that's exactly it.

    Quote Originally Posted by Resolution
    What other things are needed that you see to expand on this idea?
    If you can get a count of the parameters, at least up to the last reference parameter, then you can create a generated code snippet...
    PHP Code:
    class MyClassDecorator {
        ...
        function &
    myFunc(&$p1, &$p2) {
            
    $parameters func_get_args();
            
    $parameters[0] = &$p1;
            
    $parameters[1] = &$p2;
            return 
    Decorator::_invoke('myFunc'$this->_wrapped$parameters);
        }

    Everything else should just work, it is just that the code generator needs access to this parameter count information. Getting that is expensive unless manually entered, so a caching scheme is needed.

    The reflection API in PHP5 gets around this, so the motivation to fix it is a bit weak right now.

    yours, Marcus
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things

  22. #22
    Non-Member
    Join Date
    Jan 2004
    Location
    Planet Earth
    Posts
    1,764
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I'm having enough trouble as it is to follow up on Design Patterns Marcus, without you using Unit Testing in your examples

    Not being ungrateful of course, but some of us do have trouble following some of the stuff discussed here

    Anyways, in your example script...

    PHP Code:
    .
    .
    .
    function 
    generateCode($class$decorator) { 
            
    $code  "class $decorator {\n"
            
    $code .= "    function $decorator(&\$wrapped) {\n"
            
    $code .= "        \$this->_wrapped = &\$wrapped;\n"
            
    $code .= "    }\n"
            foreach (
    get_class_methods($class) as $method) { 
                
    $code .= "    function &$method() {\n"
                
    $code .= "        \$parameters = func_get_args();\n"
                
    $code .= "        return Decorator::_invoke(\$this->_wrapped, '$method', \$parameters);\n"
                
    $code .= "    }\n"
            } 
            
    $code .= "    function &getDecorated() {\n"
            
    $code .= "        return \$this->_wrapped;\n"
            
    $code .= "    }\n"
            
    $code .= "}\n"
            return 
    $code
        } 
    .
    .

    I assume the calling EVAL would execute the generated code yes ?

  23. #23
    ********* Victim lastcraft's Avatar
    Join Date
    Apr 2003
    Location
    London
    Posts
    2,423
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Hi...

    Quote Originally Posted by Widow Maker
    I'm having enough trouble as it is to follow up on Design Patterns Marcus, without you using Unit Testing in your examples
    If you have the SimpleTest library, the script should run straight out. Unit testing actually makes things a lot easier, but fair dues, it does take time to acclimatise .

    Quote Originally Posted by Widow Maker
    I assume the calling EVAL would execute the generated code yes ?
    Yes, it all happens on the fly.

    yours, Marcus
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •