SitePoint Sponsor

User Tag List

Page 1 of 2 12 LastLast
Results 1 to 25 of 59

Hybrid View

  1. #1
    SitePoint Guru OfficeOfTheLaw's Avatar
    Join Date
    Apr 2004
    Location
    Quincy
    Posts
    636
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    How do you handle data access?

    Recently, I've been working at ways to make more efficient database modules, specifically on a recent application as it needs the ability to either write the data to a database (must be any compatible database) or alternatively, either send the data via encrypted email or store on the server in xml based files.

    I've started with a class representing each table with insert, update, and delete methods, and specific finder methods (findById, findByName, etc). On top of this, I have a set of classes representing the divided sections of the data which are grouped together. This has 2 public functions, store and retrieve, which call private methods that know how to access each of the table data gateways. These classes also store foreign keys, which help keep track of foreign key relationships. Finally, at the top layer, there's a single object which calls these sections, giving a single point of access to the layer via a store method which takes an array of the key values of data, and also returns the requested data from a retreive method that accepts an id as an array.

    So far this has worked good with ADODB being used in the table data gateway objects to handle database interaction using standard SQL that should be compatible accross database types (and using the ADODB methods to generate SQL queries). However, I can edit the objects and swap ADODB for PEAR:B, or even some custom made DB abstraction layer.

    Only problem is that there's a class for each table, and it's tedious if dealing with a large databse with complex table relationships. I refactored most of these classes into single classes based on similarities such as foreign key relationships and such, but there still needs to be derived objects to handle specific finder methods such as findByLastName, which wouldn't exist in something such as an assets table.

    Also, joins are tricky too. I'd like to keep them made through the data access layer to avoid writing any direct SQL, but doing it by combining a set of objects seems haphazard and inefficient as it requires multiple queries rather than just one.

    How do you do it?

  2. #2
    simple tester McGruff's Avatar
    Join Date
    Sep 2003
    Location
    Glasgow
    Posts
    1,690
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Experimenting with Phrasebooks & generic DAO.

  3. #3
    SitePoint Guru
    Join Date
    Oct 2001
    Posts
    656
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    OfficeOfTheLaw, I think that a majority of the people on this forum are wrestling with this question, myself included. Data management through OO just does not cut it.

    It works reasonably well when one class maps 1-to-1 to a single table: you can use one mapper class for each class / table. But as soon as joins become involved, complexity grows exponentially. You could write special mapper classes that join tables together, but the number of classes you would have to write for the join combinations keeps growing.

    Even if you get all of that working with reasonable performance, you still have to consider the following common situations:
    - If I just want to retrieve a few properties of an object instead of all its properties, how do I do that?
    - How do I get something like the average of a property, grouped by some column?
    - How do I prevent loading an object twice, leading to inconsistent results?
    - How do I handle complex query criteria (like 'WHERE x = y AND a BETWEEN b AND c')?
    (- Even more I have encountered, but can't think of right now..)

    When you do manage to code solutions to all of these questions, you will find yourself building almost a complete database management system on top of another one: the SQL DBMS. I don't think anyone can convince me that it makes sense to build a system on top of another system that does almost exactly the same.

    So, is that a code smell here..?

    For the sake of discussion if you will, forget for a moment that you must have your data as objects in your database. In other words, step out of the all-things-must-be-OO paradigm for a moment. A good DBMS will have answers to some of your problems:
    - 'joining classes'
    You could define a view that joins two tables and easily retrieve data from this view as if it were one table.
    - 'retrieving just a few properties'
    Simply do a basic select property1,...,propertyN for the properties you want to retrieve
    - 'averages of properties'
    A simple AVG() ... GROUP BY etc will do just fine.
    - 'loading an object twice'
    If you leave the data management to the DB and don't work on in-memory copies of 'objects', you will never have this problem.
    - 'complex queries'
    You have two options when you insist on doing it OO:
    1) write a findByX method for each type of query you want to perform
    2) write your own query parser to translate to SQL
    But again, you can just send the DBMS a SQL query and it will do all the parsing for you, no matter how complex your query grows.

    As you can see, (at least some of) the problems we are facing with our OO data management 'so-called solutions' have already been solved in the past: the solutions are in SQL DBMS's. So why are we so insistent on reinventing the wheel? (I'm not a psychology expert, so don't ask me.)

    Unfortunately most of us PHP'ers (myself included, again) grew up with MySQL, a DBMS that lacks features like views and stored procedures. As I demonstrated above (at least for views), these features are solutions to the problems we are trying to solve. But because MySQL does not have them, we PHP'ers have to mock them up ourselves.

    So, what would be an answer to your question? Maybe keep an open mind to using the DBMS to its full potential instead of writing half of its functions on top of it in PHP.

    Of course you can still abstract the actual SQL queries behind methods in something like a static ClassNameHereSQL or DataAccess class. Don't be afraid if that class turns out to be a procedural class. If you use DBMS features like Views (which you can find in the 'other' open-source DBMS PostgreSQL), I think you will be positively surprised how well your data access code works and how well it will perform.

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

    Funnily enough I've just written an article on this . I am going to ramble a bit, but here are the extremes...

    Quote Originally Posted by Captain Proton
    Data management through OO just does not cut it.
    It's not so much the OOness, but the passive tabular versus the active singular. If most of the data you use does not affect the flow of the program, it just gets sent to the screen, then building an object model on it is a waste of time. I wouldn't even have one table for each class. Just have a tabular object, usually called a RecordSet, and have the RecordSet filled from a SQL query. Now the catch here is that you will need types to ensure that the data is correctly written back, but there are advantages to that too. You can use those types to display the data. I tried to describe such a system here...

    http://www.sitepoint.com/forums/show...5&postcount=48

    It's very efficient for CRUD apps. Make the query, add some filtering, send the results to be displayed. If you have to pull the ResultSet apart and rebuild it, things aren't so nice. If you have to pull it apart and do completely different things depending on the data, it's a bit like stripping down your engine just to turn the ignition.

    This fun begins when we are working with single items/objects that affect the flow of the code. For example logging into a system. You will need to look up the access key, find their account and check their balance possibly subtracting cash in the process, and finally cache the permissions. You probably want to change that system from time to time as well. Doing all of that with a tabular scheme will drive you nuts and yet this is a pretty simple example. Here we have the opposite situation where the queries are simple, but the logic is fiddly with drastic results.

    What do you do with a hybrid system? Just run both forms of persistence side by side. What do you do if you have lot's of complex data and the logic is complex. This is the fork in the road.

    If you go the views/stored procs. route things are initially simpler. You are working with rules rather than writing objects. The problem is that it is a mental transformation to turn behaviour into data and rules. You mostly get it wrong and the sensitivity to mistakes is high (lack of encapsulation). Now a good DB engineer wil be able to put encapsulation back in with views, and replacing joins wths stored procs. and sub selects, but that is a highly skilled process. And it doesn't respond to change very well at all. Expect a schema rebuild to take weeks and expect to get sworn at.

    Where both are complex I'd rather go the domain object route every time (I can always use ActiveRecord for the ones that are really fiddly). If the schema is changing under me I'd probably go the full DataMapper route too, but I haven't been in that situation. Everyone speaks well of that solution though.

    You have to go the domain model route if part of your data does not come from relational databases anyway. Alternatives such as full text and web services are coming on strong.

    The decider for me is that the database does not earn money. In fact it usually costs a fortune. Applications earn money and you are in for a much easier ride long term with a domain model. Business thinks in terms of actions and decisions, not data (unless the data is for sale) and they change their minds a lot. This is more apparent in the small companies where PHP normally operates, rather than larger organisations I'll admit (I usually work for small companies).

    Also once you do have some kind of persistence system that is code generated/reflective things get rather nice. That solution is limited to simpler queries though, but if I have to compromise I'll do it with the queries rather than the model anyday.

    That's a lot of options. YMMV

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

  5. #5
    simple tester McGruff's Avatar
    Join Date
    Sep 2003
    Location
    Glasgow
    Posts
    1,690
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    This is (I think) the last major topic I've still to learn on my own OOP learning curve so I may have failed to understand the scope of the problem.

    Disclaimer out of the way, in PoEAAA Fowler seems to say that the "tables" which data access classes map to aren't necessarily database tables but rather tabular data returned from the db. In that case, a table gateway class for example could roam far and wide across the db schema performing whatever single table queries or complex JOINs as are required.

    It couldn't really be that simple though, could it?

  6. #6
    SitePoint Zealot HenriIV's Avatar
    Join Date
    Jun 2004
    Location
    France
    Posts
    129
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    OfficeOfTheLaw, you should have a look at Propel http://propel.phpdb.org/wiki/ (php5 only), it's a Data Access Objects (DAO) tool

  7. #7
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    - How do I prevent loading an object twice, leading to inconsistent results?
    You use the IdentityMap pattern. It could be implemented as part of the DataMapper.
    The problem is present without an abstractionlayer btw. - the layer just makes it visible. The moment you pull data into PHP and later in the script relies on the same data being concurrent, you potentially have the trouble. So actually the abstractionlayer offers a way to solve that problem, rather than cause it.

    - How do I handle complex query criteria (like 'WHERE x = y AND a BETWEEN b AND c')?
    - If I just want to retrieve a few properties of an object instead of all its properties, how do I do that?
    - How do I get something like the average of a property, grouped by some column?
    SQL is a very precise an effecient language. No object-based abstractionlayer can beat it on home turf. Most of the time however, you will be performing rather simple queries. I believe a abstractionlayer should offer a clean object-based api for performing trivial queries, while still allowing to dig deep in and execute raw SQL. That way you get the best of both worlds. Most serious approaches to the issue I have seen so far does this.

    Using "criteria" objects as per torque (and propel) is a imho very clean and elegant way to express trivial queries. Of the examples above, atleast the first two could be expressed by this API.
    Last edited by kyberfabrikken; Dec 5, 2004 at 12:40. Reason: Oops - mixed up creole with propel

  8. #8
    SitePoint Guru OfficeOfTheLaw's Avatar
    Join Date
    Apr 2004
    Location
    Quincy
    Posts
    636
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I spent a little time today (slow sunday) trying a rewrite of the data access layer of my application processing system, and this is what I've worked out (incomplete though).

    Data access is usually only done once at particular places, such as at the end of the application, where it is stored, and when they view or review an application, which retrieves it. On retrieval, the whole mess is stored in a multi-dimensional array in the session, and this is also passed to the object for storage (and automatic updates).

    Here's what I've played with so far:

    PHP Code:
    <?php

    class Application
        
    {
        private 
    $appid;
        private 
    $uid;

        
    /*****
         * CONSTRUCT
         * @returns NULL;
         * Description: initializes the properties
         * of the application by loading each section.
         ****/
        
    public function __construct()
            {
        require_once (
    FS_CLASS_ROOT 'dbMetaData.class.php');
        foreach(
    MetaData::getMetaData() as $key => $value)
            
    $this->$key $value;

        
    $this->requireObjects();
            }

        public function 
    store(&$application$uid$appid=NULL)
        {
        
    $this->uid   $uid;
            
    $this->appid $appid;

        foreach(
    $application as $key => $value)
            {
            if((
    $key != 'appid' ) && ( $key != 'uid'))
              {
              
    $id $this->sect_ids[$key];
              
    $returnID $this->sections[$key]->store($application[$key], 
                                                   
    $this->$id); 

              
    // if the appid was returned from a new app, set it.
              
    if(($key == 'Employment')&&(!is_null($returnID)))
                
    $this->appid $returnID;
                      }
            }
        }

        public function 
    retrieve($appid)
            {
            
    $this->appid $appid;
        foreach (
    $this->sections as $key => $value)
            {
            
    $id $this->sect_ids[$key];
            
    $application[$key] =& $this->sections[$key]->fetch($this->$id);
            if(
    $key == 'ApplicationInfo')
                
    $this->uid $application['ApplicationInfo']['uid'];
            }
        
    $application['uid'] = $this->uid;
        
    $application['appid'] = $this->appid;
            return 
    $application;
            }
        
        private function 
    requireObjects()
            {
        foreach(
    $this->sections as $key => $value)
            {
            require_once (
    FS_CLASS_ROOT 'Section.class.php');
            
    $this->sections[$key] =& new Section($this->sect_tbls[$key], 
                                  
    $this->sect_ids[$key]);
            }
        require_once (
    FS_CLASS_ROOT 'TableDataGateway.class.php');
        }
        }
    ?>
    PHP Code:
    <?php

    class Section
        
    {
        private 
    $data = array();
        private 
    $tbl_name      NULL;
        private 
    $id            NULL;
        
        public function 
    __construct($tbl_name$id)
            {
            
    $this->tbl_name $tbl_name;
            
    $this->id $id;
        }

        public function 
    store($data$uid NULL$contraints=NULL)
            {
            if(
    is_null($uid))
            return 
    $this->insert($data);
            else
                
    $this->update($data$uid$contraints);
            }
        
        public function &
    fetch($uid)
            {
            
    /* Select SQL query */
              
    $sql "SELECT " TableDataGateway::_cols($this->tbl_name) .
                     
    " FROM {$this->tbl_name}
                     WHERE 
    {$this->id} = $uid";

              
    $rs =& $GLOBALS['adodb']->Execute($sql);
          
          while(!
    $rs->EOF)
               {
               
    $this->data[] = $rs->fields;
               
    $rs->MoveNext();
               }
        
        if(
    count($this->data) == 1)
            
    $this->data $this->data[0];
            
            return 
    $this->data;
            }
        
        private function 
    insert($data)
            {
        
    $sql "SELECT * FROM {$this->tbl_name} WHERE {$this->id} = -1";
            
    $rs =& $GLOBALS['adodb']->Execute($sql);
            
    $insertSQL $GLOBALS['adodb']->GetInsertSQL($rs,$data);

            
    $GLOBALS['adodb']->Execute($insertSQL);
            return 
    $GLOBALS['adodb']->Insert_ID( );
        }
        private function 
    update($data$id$constraints=NULL)
        {
        
    $data[$this->id] = $id
            
    $sql "SELECT * FROM {$this->tbl_name} WHERE {$this->id} = $id";

        
    /* constraints is an array of additional elements to
           update a record by, and contains the modifyer as the first
           element, which is the modifyer used in the SQL (AND, OR),
           and the second being a column name => value) */
        
    if(!is_null($constraints))
           {
           foreach(
    $constraints as $key => $value)
            (
    $key == 'modifyer')? $sql .= $value$sql .= $key = '$value'";
           }
            
    $rs  $GLOBALS['adodb']->Execute($sql);
        
    $updateSQL $GLOBALS['adodb']->GetUpdateSQL($rs,$data);
        
            if(
    is_string($updateSQL)) 
            
    $GLOBALS['adodb']->Execute($updateSQL);
        else
            return 
    false;
        }

        }
    ?>
    The application uses some meta data which identifies the tables, maps domain names to table names, and determine what primary keys they use.
    PHP Code:
    class MetaData
        
    {
        function 
    getMetaData()
            {
            return 
    $GLOBALS['configuration']['db_MetaData'];
            }
        }

    // configuration

    $GLOBALS['configuration']['db_MetaData']['sections'] = array(

                                 
    'Employment'           => NULL,
                                 
    'ApplicationInfo'      => NULL,
                                 
    'PersonalInfo'         => NULL,
                                 
    'AffiliatedCompanies'  => NULL,
                                 
    'EmploymentHistory'    => NULL,
                                 
    'Education'            => NULL,
                                 
    'Background'           => NULL,
                                 
    'EmployeeReferal'      => NULL,
                                 
    'Refrences'            => NULL,
                                 
    'EmpHistMisc'          => NULL,
                                 
    'Excel'                => NULL,
                                 
    'Skills'               => NULL,
                                 
    'Signature'            => NULL,

                                 );

    $GLOBALS['configuration']['db_MetaData']['sect_ids'] = array(

                                  
    'Employment'           => 'appid',
                                  
    'PersonalInfo'         => 'uid',
                                  
    'AffiliatedCompanies'  => 'uid',
                                  
    'EmploymentHistory'    => 'uid',
                                  
    'ApplicationInfo'      => 'appid',
                                  
    'Education'            => 'uid',
                                  
    'Background'           => 'uid',
                                  
    'EmployeeReferal'      => 'uid',
                                  
    'Refrences'            => 'uid',
                                  
    'EmpHistMisc'          => 'uid',
                                  
    'Excel'                => 'uid',
                                  
    'Skills'               => 'uid',
                                  
    'Signature'            => 'uid',

                                  );


    $GLOBALS['configuration']['db_MetaData']['sect_tbls'] = array(

                                  
    'Employment'           => 'app_employment',
                                  
    'PersonalInfo'         => 'app_personal_info',
                                  
    'AffiliatedCompanies'  => 'app_affiliated_companies',
                                  
    'EmploymentHistory'    => 'app_employment_history',
                                  
    'ApplicationInfo'      => 'app_application_info',
                                  
    'Education'            => 'app_education',
                                  
    'Background'           => 'app_background',
                                  
    'EmployeeReferal'      => 'app_employee_referal',
                                  
    'Refrences'            => 'app_refrences',
                                  
    'EmpHistMisc'          => 'app_employment_history_misc',
                                  
    'Excel'                => 'app_excel',
                                  
    'Skills'               => 'app_skills',
                                  
    'Signature'            => 'app_signature',

                                  ); 
    So far it's been working okay with a few bugs here and there, and this only works with the applications. There's other tables, such as account information, timestamps for applications, authentication, and the HR section configuration options. I think these should probably have their own objects as the data is handled somewhat differently, but can probably use a single point of access object like the one above to handle the interactions.

    Thoughts? I'm still trying to work out some insane set of object to do all, but spent a few minutes with Propel today and must say it's not too shabby
    [/php]
    [/php]

  9. #9
    SitePoint Enthusiast hantu's Avatar
    Join Date
    Oct 2004
    Location
    Berlin
    Posts
    54
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    What I wish to have in PHP is a port of the Persistence Broker API (http://db.apache.org/ojb/docu/tutori...-tutorial.html) of apache's OJB.

    I'm not really content with propel because I dislike the fact that my business objects need to inherit from a persistent base class. I'd prefer a reflective approach with my persistable objects only being simple classes with getters & setters. But that's just a personal preference, not a critique on propel.

    I'm currently working on a lightweight O/R mapper that supports the major features of the Persistence Broker API and works by reflection rather than code generation.


    I don't think you really need an Identity Map as everything is built up and torn down for each request, so it shouldn't be too hard avoiding double loads.

    - How do I handle complex query criteria (like 'WHERE x = y AND a BETWEEN b AND c')?
    As said before that should be no problem using criteria (Fowlers Query Object Pattern)

    - If I just want to retrieve a few properties of an object instead of all its properties, how do I do that?
    I think with automatic mapping, that's not possible. You load all properties and have the application select what it needs.

    - How do I get something like the average of a property, grouped by some column?
    OJB offers an interesting feature for this called Report Queries (http://db.apache.org/ojb/docu/guides...Report+Queries). These queries can be built via criteria and don't return objects but arrays with the data. Maybe this could be ported to PHP too.

    But I'm sure that you can't build every query with criteria objects. For more complex stuff, you just grab the database connection and do the querying & populating of the resulting objects manually.

  10. #10
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by hantu
    don't return objects but arrays with the data.
    Isn't that what mysql_fetch_assoc does?

    Douglas
    Hello World

  11. #11
    ********* 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 hantu
    I don't think you really need an Identity Map as everything is built up and torn down for each request, so it shouldn't be too hard avoiding double loads.
    That's what I thought and within five minutesI ran into a contradiction. The extra code is trivial. The only proviso is if you are iterating through a large amount of data you will want to turn it off or you will end up keeping references to every record.

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

  12. #12
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by lastcraft
    or you will end up keeping references to every record.
    Is the overhead noteable (performance-wise) ? I mean, a reference can't be taking up much memory ?

    Quote Originally Posted by hantu
    so it shouldn't be too hard avoiding double loads.
    The main purpose of IdentityMap may well be to prevent concurrency-problems, due to double-loads. There is a secondary benifit of it though, in that it effectively acts as a script-level cache. This makes it able to reduce the total ammount of data-traffic between RDBMS and PHP. I see that as a huge benifit.

  13. #13
    SitePoint Enthusiast hantu's Avatar
    Join Date
    Oct 2004
    Location
    Berlin
    Posts
    54
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Ok, I'm convinced it will make sense to implement an Identity Map

    @lastcraft:
    ... within five minutes I ran into a contradiction
    Could you give a short example?

    @kyberfabrikken
    ... that it effectively acts as a script-level cache
    I don't really understand this. How often do you fetch the same data twice within one request? I agree with lastcraft that in some cases you can avoid contradictions with an Identity Map, but I don't think it really adds a significant performance benefit as a caching mechanism.

    @DougBTX
    Isn't that what mysql_fetch_assoc does?
    Yes the result of a Report Query would exactly look like this. The benefit is you can build the query abstract defining object properties via criteria.

  14. #14
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by hantu
    Ok, I'm convinced it will make sense to implement an Identity MapI don't really understand this. How often do you fetch the same data twice within one request?
    That really comes down to how well-structured you applications.
    In theory it isn't something that ought to happen too much, but in reality it will. At least that's my experience.

  15. #15
    ********* 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 hantu
    Could you give a short example?
    Load object (say Child) that happens to be a member of another's collection (say Parent). Call getOwner() on the Child. Pass the Parent to another method that fetches all of it's children (uh oh ). The parent then does something to all of it's children .

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

  16. #16
    SitePoint Zealot sike's Avatar
    Join Date
    Oct 2002
    Posts
    174
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    Is the overhead noteable (performance-wise) ? I mean, a reference can't be taking up much memory ?
    if you keep references to objects you will run into memory problems if you work with a large number of objects because they will stay as long as the request takes. i think pear has the same problem with its destructors.. harryf posted something about this on his blog. maybe a checksum approach could work but never tried it (;

    If I just want to retrieve a few properties of an object instead of all its properties, how do I do that?
    use a proxy pattern

    I'm not really content with propel because I dislike the fact that my business objects need to inherit from a persistent base class. I'd prefer a reflective approach with my persistable objects only being simple classes with getters & setters. But that's just a personal preference, not a critique on propel.
    care to explain the benefits you see in using reflection over meta defined bos?

    cheers
    Sike

  17. #17
    SitePoint Enthusiast hantu's Avatar
    Join Date
    Oct 2004
    Location
    Berlin
    Posts
    54
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by sike
    care to explain the benefits you see in using reflection over meta defined bos?
    In the way I would like it to have, the metadata of the business objects would still have to be defined via XML like it is in propel.

    But the persistable objects themselves could be simple classes that only have getters & setters for each attribute that shall be persistable (similar to Java Beans). The mapper could get the class of an object via reflection, lookup the medata for this class and dynamically retrieve the data via get* / set* calls.

    This is the way OJB (and I think Hibernate too) handles O/R Mapping.

    The benefits would be:
    • No need to inherit from an abstract persistent base class, thus cleaner code in the business objects
    • No need to regenerate code when changes occur
    • it would be easy to abstract the data access implementation via DAO-Pattern
    • the persistable objects are very lightweight and could be used as transfer objects for upper layers
    • it would be easy to integrate such a mapper in existing applications (that use a Domain Model approach)


    possible drawbacks:
    • slower code caused by reflection & runtime analysis of the objects
    • difficult tracking of changes in the objects

  18. #18
    SitePoint Wizard DougBTX's Avatar
    Join Date
    Nov 2001
    Location
    Bath, UK
    Posts
    2,498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by hantu
    The mapper could get the class of an object via reflection, lookup the medata for this class and dynamically retrieve the data via get* / set* calls.
    It can be simpler than that if you use __get and __set. All that needs to be done is for the object to be given an array of data when it is constructed, and then use __get and __set to retrieve it. Add some code to record which properties have been modified in __set, and you have all the info you need to do an UPDATE SQL call.

    Code with tests:

    PHP Code:
    class GetSetter {
        
        protected 
    $data;
        
        function 
    __construct($data) {
            
    $this->data $data;
        }
        
        function 
    __get($key) {
            if (isset(
    $this->data[$key])) {
                return 
    $this->data[$key];
            } else {
                throw new 
    Exception('Property does not exist!');
            }
        }
        
        function 
    __set($key$value) {
            if (isset(
    $this->data[$key])) {
                
                
    $this->data[$key] = $value;
                
                
    // Insert code here to record
                // that $key has been modified
                
            
    } else {
                throw new 
    Exception('Property does not exist!');
            }
        }
        
    }

    class 
    TestCaseGetSetter extends UnitTestCase {
        
        function 
    testRetrieve() {
            
    $data = array (
                
    'name' => 'Steve',
                
    'age'  => 12,
                
    'location' => 'UK'
            
    );        
            
            
    $get_setter = new GetSetter($data);
            
            
    $this->assertEqual($get_setter->name'Steve');
            
    $this->assertEqual($get_setter->age12);
            
    $this->assertEqual($get_setter->location'UK');
        }
        
        function 
    testSave() {
            
    $data = array (
                
    'name' => 'Steve',
                
    'age'  => 12,
                
    'location' => 'UK'
            
    );        
            
            
    $get_setter = new GetSetter($data);
            
    $get_setter->age 13;
            
    $this->assertEqual($get_setter->age13);
        }
        

    Code generation isn't really needed when you have dynamic typing.

    Douglas
    Hello World

  19. #19
    SitePoint Enthusiast hantu's Avatar
    Join Date
    Oct 2004
    Location
    Berlin
    Posts
    54
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by DougBTX
    It can be simpler than that if you use __get and __set. All that needs to be done is for the object to be given an array of data when it is constructed, and then use __get and __set to retrieve it. Add some code to record which properties have been modified in __set, and you have all the info you need to do an UPDATE SQL call.
    That exactly what I don't want to have. The business objects should be oblivious how and if they are persisted. I don't want persistence layer specific code inside them.


    @lastcraft

    thanks for the example, I see now what you meant. Will surely use an Identity Map.

    To the question of types: I will surely implement a typing system. I'm using creole as underlying db abstraction layer, which already has a rudimentary type system built in.

  20. #20
    ********* 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 hantu
    This is the way OJB (and I think Hibernate too) handles O/R Mapping.
    Hibernate uses XML, but generates it for you from XDoclet. Most JDO implementations follow that route.

    You will still need types by the way. Partly so that you can construct your update queries correctly, but also so that you can instantiate objects such as Date and Money when you pull your data.

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

  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 kyberfabrikken
    Is the overhead noteable (performance-wise) ? I mean, a reference can't be taking up much memory ?
    It's just that if you are processing a line at a time, PHP cannot garbage collect data you have already worked through.

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

  22. #22
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    slower code caused by reflection & runtime analysis of the objects
    Probably true - however, we need actual benchmarks to be certain about that. Does anybody know enough about the internals of PHP to come with a qualified statement about this ?

  23. #23
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    if you keep references to objects you will run into memory problems if you work with a large number of objects because they will stay as long as the request takes
    Point taken.

    The event will be rare though, depending of the character of your application.
    For those cases we might allow to bypass the caching for a particular request.

  24. #24
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    To the question of types: I will surely implement a typing system. I'm using creole as underlying db abstraction layer, which already has a rudimentary type system built in.
    How do you guys get around this ? I've been using a setup where I wrap each value in a class descending from Attribute, eg. AttributeString, AttributeInteger etc. The Attribute class has getValue and setValue, plus some methods to convert to/from SQL/PHP (and XML btw.). It works well, but i'm a bit concerned about the overhead created by it.

  25. #25
    ********* 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 kyberfabrikken
    It works well, but i'm a bit concerned about the overhead created by it.
    I wouldn't even hazard a guess and would wait until it's a problem. If you cannot notice it, it's probably not a problem.

    Have you measured the overhead? I suspect that you will be underwhelmed. I found with SimpleTest, that instantiates expectation classes for just about every assertion, that the other code (especially the initial PHP parse) dwarfed the small object handling. I would expect this in turn would be dwarfed by DB access in persistence code.

    Measure it just in case.

    As for memory, one of the worst offenders seems to be PHP hashes. I have seen factor of six increases in the data size once loaded into a hash. You just cannot predict these things, you have to measure .

    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
  •