SitePoint Sponsor

User Tag List

Page 2 of 2 FirstFirst 12
Results 26 to 49 of 49
  1. #26
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    989
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    Quote Originally Posted by sunwukung View Post
    Forgive me for being dense Tom - but how would you achieve this?
    Do you load the entire object graph on the initial query?
    Are you using some sort of magic method (i.e. __get) to instantiate mappers when data that points to relations is requested?

    Chaining (as I understand it) would involve returning an object of some sort - be it the original root object, or I guess you're drilling down to each object in some way?
    I posted a full code solution :P

    Yes it uses __get.

    Essentially:

    -Each object knows of the mapper it was created by.
    -Each mapper knows how it is related to other mappers
    -When you call __get it queries the related (via its own) mapper to find the related record(s).
    -As each object is created, it is passed the mapper it was created by. This allows for unlimited chaining


    It also means any object, at any level of the chain can be initiated through its mapper and used as the root object.

  2. #27
    SitePoint Zealot
    Join Date
    Feb 2009
    Location
    Bristol
    Posts
    116
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    DoH! sorry - post #12 - I forgot to scroll. thanks for humouring me!

  3. #28
    SitePoint Enthusiast
    Join Date
    Feb 2005
    Posts
    27
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Pheasant uses __get to dynamically traverse relationships. I assume TomB is doing something similar.
    Last edited by Lachlan; Jul 13, 2010 at 17:05. Reason: Ooops! Missed Tom's response!

  4. #29
    SitePoint Zealot
    Join Date
    Feb 2009
    Location
    Bristol
    Posts
    116
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I figured as much - I've been rolling my own as well - mostly out of academic interest. Writing a Data Mapper is an interesting project, so many issues to resolve: magic or concrete accessors, convention vs configuration, Active Record or Datamapper.

    My wife finds me completely unintelligible these days - although some times she does say "Is that from Fowler?".


    Something's bothering me about passing the Mappers to Entities. Is there a potential for a memory leak? Is this a potential issue for DI too?

    http://paul-m-jones.com/archives/262

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

    How is that a bug? That's exactly what you would expect from a reference counting collector.

    The "fix" of manually calling __destruct() is bogus. It means anyone else holding the reference will get the destructor run even if you haven't finished with it. Very mysterious behaviour, especially if it gets run twice.

    A better solution is to not hang on to such references if you are creating a lot of objects. Pass by value, or pass the reference into the method so you only have linkage for the life of the method.

    The fewer invisible threads connecting things the better. Linking everything to everything else may save a few function parameters and may look superficially neat. Longer term the code is much harder to reason about. It's not just the garbage collector that will get it wrong.

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

  6. #31
    SitePoint Zealot
    Join Date
    Feb 2009
    Location
    Bristol
    Posts
    116
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by lastcraft View Post
    How is that a bug?
    I'm not sure, that's why I'm asking you lot...

    Quote Originally Posted by lastcraft View Post
    The fewer invisible threads connecting things the better. Linking everything to everything else may save a few function parameters and may look superficially neat. Longer term the code is much harder to reason about. It's not just the garbage collector that will get it wrong.
    In that respect, is the chaining method TomB described earlier a step towards this?

  7. #32
    ********* 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 sunwukung View Post
    I'm not sure, that's why I'm asking you lot...
    Sorry, not a criticism of you. I was thinking of the blog post and that it was filed as a bug. Very strange.

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

  8. #33
    SitePoint Enthusiast
    Join Date
    Feb 2005
    Posts
    27
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yup, avoid circular references at all costs. That is one of the main reasons I use the central Pheasant object, such that it maintains associations between domain objects and mappers and finders.

    PHP 5.3 does a much better job at detecting circular references when it's freeing memory, but I still don't trust it

  9. #34
    SitePoint Zealot
    Join Date
    Feb 2009
    Location
    Bristol
    Posts
    116
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Now this is interesting.
    The stumbling block I have with TomB's method - and with the whole mapper thing - is handing off dependencies to relations.
    How do you provide the required Mapper to the Entity? In Tom's example, the mapper is generated from scratch internally - which is (as I understand it) a Unit Testing no no. Next step - as TomB indicates - is to use a factory. So now, you're handing a factory or DI container to the Entity, at least all your responsibilities for object generation are handled in one place.

    But as you iterate down through the chain, handing database adapters and factories down the chain, you're setting up all these references right? Worse still, I like to try and cache resources that have been instantiated in my factory classes, so if the generated object contains a reference to the factory itself, bingo - there's a circular reference.

    The latest idea I'm thinking of trying is a proxy wrapper as suggested by Anthony Sterling which contains the factory and injects the required mapper in much the same way as TomB invokes the getMapper method - just handled externally by the wrapper i.e.

    PHP Code:
    class Proxy{

    private 
    $entity;
    private 
    $factory;

    public function 
    __construct($entity,$factory){
        
    $this->entity $entity;
        
    $this->factory $factory;
        
    $this->relations $this->entity->getRelations;
        
    $this->properties $this->entity->getProperties;
        }

    public function 
    __get($key){
        if(
    array_key_exists($this->properties)){
           return 
    $this->entity->$key;
        }

        if(
    array_key_exists($this->relations)){
            
    $mapper $this->factory->getMapper($key);
            
    $child_entity $mapper->load($Key,array($this->relations['foreignField']=>$this->relations['localField']));//at this point, the child entity is cached
            
    return $this->factory->getProxy($child_entity,$this->factory);
        }


    So, should I ditch the caching to prevent circular references? Should I ditch the proxy and just generate objects with "new" (if there is a specific method for generating the mapper, then I guess it's testable right?).
    Which brings us round to the last issue - if the entity classes have the capability for generating mappers, then they have the capacity to save themselves - albeit via the mapper (the syntactic sugar) - is this a corruption of data mapper. If the mapper is generated by a factory then we can alter the mapper they recieve externally through configuration right?

    Man, my head is spinning - anyone want to get me out of this tangle?

  10. #35
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    989
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    On all this circular references/memory usage I really have to ask: How much memory is actually being lost in a real world application. Is it hitting the memory limit or causing server issues? PHP scripts generally run in less than a second. The memory usage on the server isn't going to be high unless it's handling a hell of a lot of requests at once.

    Doing the test script provided with a 1m memory limit:

    PHP Code:
    <?php
    ini_set
    ('memory_limit''1M');
    class 
    Foo {
        function 
    __construct()
        {
            
    $this->bar = new Bar($this);
        }
    }

    class 
    Bar {
        function 
    __construct($foo null)
        {
            
    $this->foo $foo;
        }
    }

    $count 0;
    while (
    true) {
        
    $foo = new Foo();
        unset(
    $foo);
        echo 
    $count++ . '::' number_format(memory_get_usage()) . "\\n";
    }
    ?>
    On PHP 5.3 on windows this gets to the 1624th iteration. That's with a 1mb limit. How many scripts are going to create anywhere near this many cyclic references?

    To me, it seems like mountain/molehill. If you're that worried about memory usage I'd argue you shouldn't be using OOP in the first place :P

  11. #36
    SitePoint Zealot
    Join Date
    Feb 2009
    Location
    Bristol
    Posts
    116
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    It's all about balance isn't it?
    On the one hand, we spend a lot of time here <ahem>debating</ahem> about best practices and the "correct" interpretation of design patterns - then we are confronted with the real world, throw the rule book out and assume that we'll get away with it.

    In that case, why not just use global variables for common system resources (like factories) rather than worry about all this dependency injection/orthogonality schtick?



    There are three issues:
    1: how to handle the dependency (i.e. finding/generating the mapper for the relation)
    2: Is it a corruption of the data mapper pattern - why not just use $user->save() if it has access to it's own mapper?
    3: The (albeit apparently negligible) circular references generated by passing factories or DI containers that cache previously instantiated objects.

  12. #37
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    989
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    Quote Originally Posted by sunwukung View Post
    It's all about balance isn't it?
    On the one hand, we spend a lot of time here <ahem>debating</ahem> about best practices and the "correct" interpretation of design patterns - then we are confronted with the real world, throw the rule book out and assume that we'll get away with it.

    In that case, why not just use global variables for common system resources (like factories) rather than worry about all this dependency injection/orthogonality schtick?

    Well that creates scalability, maintenance, reusability and code clarity issues. The cyclic reference thing is entirely performance. As far as I can tell it's not *bad* in terms of software design. It doesn't present any more design issues than any other dependency. I'm welcome to being proved wrong here.



    2: Is it a corruption of the data mapper pattern - why not just use $user->save() if it has access to it's own mapper?
    Does $user->save(); present any real issues? In ActiveRecord it does because you can only save to a single data source and the $user object has knowledge of it.

    It probably is outside the bounds of the pattern... but isn't that just argumentum ad antiquitatem?

  13. #38
    SitePoint Zealot
    Join Date
    Feb 2009
    Location
    Bristol
    Posts
    116
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    So, you could theoretically pass the whole ORM layer around then? OOF!! I digress -

    How would/do you handle the new mapper issue? I noticed that you put a comment in your original code that suggested using a factory? How would you pass the factory round? I guess you pass it in at the top of the chain, and each step in the chain passes it's factory (and database adapter) down the chain?

  14. #39
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    989
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    Ok let me put a disclaimer on this, i'm not saying this is the right way of doing it, only how I'm handling it because the convenience of unlimited chaining on data objects is better than any downsides I've come across yet.

    PHP Code:

    class MapperFactory {
        protected 
    $dic;
        protected 
    $mappers = array();
        
        public function 
    __construct(Injection $dic) {
            
    $this->dic $dic;    
        }
            
        public function 
    getMapper($name) {
            if (!isset(
    $this->mappers[$name])) $this->mappers[$name] = $this->dic->create($name);
            return 
    $this->mappers[$name];
        }    
    }

    class 
    DataMapper {
        
        public function 
    __construct(MapperFactory $factory) {
            
        }

    Yes it has the exact same issue as you had. It creates a cyclic reference because it maintains a single instance of any given mapper.

  15. #40
    SitePoint Zealot
    Join Date
    Feb 2009
    Location
    Bristol
    Posts
    116
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    In your defense Tom, I've not seen an example of Data Mapper (taken beyond the typical load->user example) that doesn't run into this problem.

    The question I asked was not how you would construct the factory itself, but how you would provide it to the entity? In your earlier example, getMapper was called when __get intercepted a key in the relations array. That method instantiated the required mapper there and then. If you were to replace this with a factory, would you pass the factory into the entity?

    • At construction(and thus down the chain),
    • Using static calls
    • or would you instantiate a new one (which would defeat the purpose).


    At the moment, I'm toying with this proxy wrapper idea Mr.Sterling proposed earlier - so it intercepts the relations key, uses it's instance of the factory object to generate the required objects, then wraps them up in another proxy/factory combo before returning it to the client.

  16. #41
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    989
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    Sorry I did update the code in a ninja-edit :P I pass the factory down the chain via the constructor. I'm not sure i fully understand the proxy/wrapper idea. Can you show how you'd go about this?

  17. #42
    SitePoint Zealot
    Join Date
    Feb 2009
    Location
    Bristol
    Posts
    116
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    check post #34...
    It wraps the entity in an object which also contains the factory. The proxy grabs relations and properties keys from the entity, and intercepts access, either returning the relevant entity data, or generating new entities further down the chain.

  18. #43
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    989
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    How would that work in terms of the mapper/entity relationship? Do you pass the proxy to each entity instead of the mapper? Does each mapper have its own proxy?

    edit: Are you saying

    $userMapper->findById(123);

    would return a Proxy object?

  19. #44
    SitePoint Zealot
    Join Date
    Feb 2009
    Location
    Bristol
    Posts
    116
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    yes & no - the fact that you've called the calling variable $userMapper is kind of wrong,

    I start with a wrapper for the whole system - let's call it ORM

    PHP Code:
    $User1 $ORM->load('user',1); 
    this makes a call internally to the factory, which returns a proxy containing the entity and a reference to itself. So if the client calls

    PHP Code:
    $Post $User1->post[1
    it would hit the proxy first, which would use it's factory to generate the relevant objects, and return them wrapped in another proxy. All this does really is keep the dependencies out of the entities - for whatever that is worth.

  20. #45
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    989
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    Well, "$userMapper" is retrieved using $mapperFactory->getMapper('User'); (or in actual fact I create a shorthand $this->mapper->user, I can even do
    $this->mapper->user[123]; but this is not relevant )

    I like the idea, but as with most things it creates new problems.

    In this case you've broken any possible contracts with the data object.

    PHP Code:
    public function placeOrder(Basket $basketUser $user) {
        
    }

    $user $userMapper->findById(123);
    placeOrder($this->session->basket$user); 
    Because your $user is now an instance of Proxy this wont work.

    In my datamapper. The mapper does something like:

    PHP Code:
    $name str_replace('Mapper'''get_class($this);
    if (
    class_exists($name)) return new $name;
    else return new 
    DataObject

    Sometimes it's useful for the returned object to be a type . This way I can either create a "User" class or
    let it be handled by the generic DataObject class. For example an Order object may warrant a getTotal() function which loops through the order items/quantities and calculates the total.

  21. #46
    SitePoint Zealot
    Join Date
    Feb 2009
    Location
    Bristol
    Posts
    116
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    This OOP business is like blumming "whack-a-mole".

    It's what I love and hate about OOP - it exposes the "design" aspect of programming. A decision is required, it's often not perfect and there are positive and negative consequences. To make skillful (and I guess you could say "beautiful") choices is what we're aiming for.

    Cheers for the input, I'm off to buy some strong booze.

  22. #47
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    989
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    Yeah, It's somewhat annoying. There's generally never a "correct" solution and mostly it will vary based on individual goals and how people rank the relative pros/cons.

    On a sort of related topic, php needs an __instanceof magic method. This would allow us to properly create wrapper classes since we already have __get, __set and __call.

  23. #48
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by TomB View Post
    Because your $user is now an instance of Proxy this wont work.
    Unless the proxy extends a class that implements the User interface.

  24. #49
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,147
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    Quote Originally Posted by TomB
    Yeah, It's somewhat annoying. There's generally never a "correct" solution and mostly it will vary based on individual goals and how people rank the relative pros/cons.
    Here's to circular conversations.
    The only code I hate more than my own is everyone else's.


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
  •