SitePoint Sponsor

User Tag List

Page 1 of 4 1234 LastLast
Results 1 to 25 of 110

Hybrid View

  1. #1
    SitePoint Guru
    Join Date
    Dec 2003
    Location
    oz
    Posts
    819
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Single object or multiple objects per busines object

    I know this has been argued to death, but I've been reconsidering my approach to data access. I currently have a business object and a data mapper for each business object. I'm thinking however that a single object per business object might be the best idea. That way everything is done for a business object is done within the object. It simplifies the system - which I think is an important issue. Alot of systems/frameworks over-complicate things while trying to abstract out layer over layer. I can see the advantage of layers, but can also see the disadvantage with complications of too many.

    The simplicity also means that almost anyone can start modifications on an application becuse it only really has 3-4 layers now. And as far as interchanging a database - well each db has it's own query specifics, so you cant do that anyway.

    What got me to thinking about this was a link to nakedobjects (http://www.nakedobjectsorg/content.html)- which points out that a logical entity should really be an entity. It can have agregate objects to help it do its job/s, but it should be *the* place of contact for all things relating to that entity - with all the processing hidden inside it. This idea really makes sense to me.

    By seperating the data access and the business object, you are seperating the data and the behaviour - which really loses the point of encapsulation. Especially as any modification to the business object also means modification to the mapper.

    Another reason for having a mapper seperately is that a customer object, say, logically shouldn't return an iterator of a list of customer objects.
    Eg BusinessObjectIterator Customer.GetByName(string name).
    But really, why the hell not?

    This of course leaves a couple of issues. Such as if the data access is in the object itself, not every object can have a live connection like each data-mapper. I guess it could initiate a connection each time access to the db is required. A singleton is a problem as you will often require more than one connection at a time - and pooling seems too much work to deal with.

    So I guess my points originally for seperating was:
    1. help make it easy to interchange db's
    2. doesnt make sense for an object to return a iterator/list of objects
    3. keep a class small so it's easy to find aspects you need to find quickly
    4. have a class do just one thing.
    5. a connection in each business object is ALOT of connections.

    Now I'm thinking that these issues are really non-issues (except the last one) and that encapsulation is really the essence of OOP - ie everything is an entity unto which all of its data and behavior are coupled behind an interface.

    Any thoughts/comments?
    And any soln to the problem of having a db conn in each business object?

    Regards,
    Eli

  2. #2
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I'll give you a couple of thoughts because you have lots of questions buried in there. First, I think in PHP your goal should be to do the simplest thing that makes sense for the application you are building; second I think your goal should not be to achieve some pattern or abstraction, but to produce a codebase that meets your feature, performance and maintainability targets; and third you should not try to code Java in PHP.

  3. #3
    SitePoint Addict
    Join Date
    May 2003
    Location
    Calgary, Alberta, Canada
    Posts
    275
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by lazy_yogi
    I know this has been argued to death, but I've been reconsidering my approach to data access. I currently have a business object and a data mapper for each business object. I'm thinking however that a single object per business object might be the best idea. That way everything is done for a business object is done within the object. It simplifies the system - which I think is an important issue. Alot of systems/frameworks over-complicate things while trying to abstract out layer over layer. I can see the advantage of layers, but can also see the disadvantage with complications of too many.
    Ive been researching and toying around with different options for data access in php for a long time and quite enjoy threads about this. . My personal preference is a business object and a mapper. Most people here I think like active records.

    Quote Originally Posted by lazy_yogi
    By seperating the data access and the business object, you are seperating the data and the behaviour - which really loses the point of encapsulation. Especially as any modification to the business object also means modification to the mapper.
    Im not sure I would group data access with the behavior of a business object. To me this is seperate.
    Quote Originally Posted by lazy_yogi
    Another reason for having a mapper seperately is that a customer object, say, logically shouldn't return an iterator of a list of customer objects.
    Eg BusinessObjectIterator Customer.GetByName(string name).
    But really, why the hell not?
    I dont see this as a problem if you are using an active record. This is usually seperated into a new object with common "find" operations for simplicity but should only be done if seperating these operations actually makes things simpler.

    Quote Originally Posted by lazy_yogi
    This of course leaves a couple of issues. Such as if the data access is in the object itself, not every object can have a live connection like each data-mapper. I guess it could initiate a connection each time access to the db is required. A singleton is a problem as you will often require more than one connection at a time - and pooling seems too much work to deal with.
    This is one of my biggest problems with using an active record or similar.
    Quote Originally Posted by lazy_yogi
    So I guess my points originally for seperating was:
    1. help make it easy to interchange db's
    2. doesnt make sense for an object to return a iterator/list of objects
    3. keep a class small so it's easy to find aspects you need to find quickly
    4. have a class do just one thing.
    5. a connection in each business object is ALOT of connections.

    Now I'm thinking that these issues are really non-issues (except the last one) and that encapsulation is really the essence of OOP - ie everything is an entity unto which all of its data and behavior are coupled behind an interface.
    1. Using a mapper or not, isnt going to make changing databases easier. There should be another layer there.
    2. You dont instatiate a new Customer to find Customers, you instantiate a new CustomerFinder to find Customers.

    I think 3,4 and 5 are still issues but of course it depends on what you need you system to do. I wouldnt say a program that follows or doesnt follow those rule is right or wrong.


    My current method for data access is similar to jdo (arborint may not be happy ). I pass an object I call PHPDataMapper ($pdm) through my controller like most people pass a db object. There is a single data mapper for all objects, instead of 1 mapper for each object.

    There are advantages and disadvantages.

    Its alot to explain so heres an example:
    PHP Code:
    // find all customers
    $query $pdm->newQuery('Customer');
    $customers $query->execute();
    foreach(
    $customers as $customer) {
        echo 
    $customer->getName();

    // find all customers named "Joe"
    $query $pdm->newQuery('Customer''name == ?');
    $customers $query->execute('Joe');
    foreach(
    $customers as $customer) {
        echo 
    $customer->getName();

    // find all posts
    $query $pdm->newQuery('Post');
    $posts $query->execute();
    foreach(
    $posts as $post) {
        echo 
    $post->getTitle();

    // find all customers age's and id's in Customer Objects
    $query $pdm->newQuery('Customer');
    $query->setResult('id, age');
    $customers $query->execute();
    foreach(
    $customers as $customer) {
        echo 
    $customer->getId();

    // insert a role
    $role = new Role();
    $role->setDescription('A Role');
    $pdm->insert($role);

    // update a role;
    $role->setDescription('A Role (Revised)');
    $pdm->update($role);

    // delete a customer
    $delete $pdm->newDelete('Customer''id == ?');
    $delete->execute(1); 

  4. #4
    SitePoint Enthusiast
    Join Date
    Apr 2004
    Location
    US
    Posts
    51
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Brenden Vickery
    My current method for data access is similar to jdo (arborint may not be happy ). I pass an object I call PHPDataMapper ($pdm) through my controller like most people pass a db object. There is a single data mapper for all objects, instead of 1 mapper for each object.
    Can you show the detail implementation of PHPDataMapper? Im really interested to see how the whole things fit together.

    An example script with a actual "Object" <> "Mapper" would be every nice.

    Thanks for your help

  5. #5
    SitePoint Enthusiast hantu's Avatar
    Join Date
    Oct 2004
    Location
    Berlin
    Posts
    54
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Hi there guys, another one of this wonderful data access strategy threads

    In the last discussions I stated that I'd like to have an O/R-mapper that doesn't use code generation and is non-intrusive (which means the persistable domain objects don't need to inherit from a certain class). I'm still developing such a thing and have finished about 40% maybe

    I'm parsing an XML-File similar to the ones shown above, populating "ClassDescriptor", "FieldDescriptor" and "ReferenceDescriptor" objects from this and save them serialized on the disk. The XML-File will only be parsed again, when changes occured and the serialized data is lazy loaded by my application.

    I'm quite inspired by apache OJB and try to provide a similar API. That means your persistable objects are very simple lightweight classes with only properties and setter/getter methods.

    Queries will be done using a Query Object combined with Criteria objects similar to propels. The required joins will be generated automatically (including m:n associations)

    a short example: a product can be bought by many buyers:

    Code:
         <mapping> 
           <class name="Product" table="product">
             <field name="Id" column="id" type="integer" primaryKey="true" />
             <field name="Name" column="name" type="string" required="true" />
             <field name="Price" column="price" type="double" />       
             <many2ManyReference 
               name="buyers"
               targetClass="Buyer"
               indirectionTable="product2buyer" 
               indirectionColumnForThis="p_id"
               indirectionColumnForTarget="b_id"           
              />          
           </class>      
           <class name="Buyer" table="buyer">
             <field name="Id" column="id" type="integer" primaryKey="true" />
             <field name="Firstname" column="firstname" type="string" />
             <field name="Lastname" column="lastname" type="string" required="true" /> 
             <field name="LastLogin" column="lastlogin" type="date" />   
           </class> 
        </mapping>
    Say you want the products bought by someone ordered by name:

    PHP Code:

    $criteria 
    = new Criteria();
    $criteria->addEqualTo('Buyer.Firstname''Franz');
    $criteria->addEqualTo('Buyer.Lastname''Schwanz');

    $query = new QueryByCriteria('Product'$criteria);
    $query->addOrderByAscending('Name');

    $broker = new PersistenceBroker();

    $products $broker->getByQuery($query); 
    The generated SQL would look like the following:

    SELECT A0.* FROM prd A0
    JOIN product2buyer B0 ON A0.id = B0.p_id
    JOIN buyer A1 ON A1.id = B0.b_id
    WHERE A1.firstname = 'Franz' AND A1.lastname = 'Schwanz'
    ORDER BY A0.name ASC


    I hope my approach has become clear. I'd appreciate any comments, suggestions, critic. If my app will be finished one day (and be performant enough) I'd like to release it licensed by LGPL. If anybody would like to help developing, I'd appreciate this!

  6. #6
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I think we should clarify difference between separatiing what you call "business logic" and what you call "database access" from the implementation of that separation. Putting them in separate classes and putting a shared mapper in between as above may achieve less separation than a simple DAO because the mapper becomes brittle.

    I know the Java folks use fancy terms like "business logic" but I think PHP programmers might divide things more simply as below and implement one or more separations depending on the application.

    SQL - these are the application specific commands sent to the database and the assinging variables into those commands.

    Code - the code that works on data coming from or going to a database.

    DB - this is the library of functions uses to access the database.

    Libraries - reusable code that is not application specific

  7. #7
    SitePoint Addict
    Join Date
    May 2003
    Location
    Calgary, Alberta, Canada
    Posts
    275
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I want to point out that its not just the Java people who have fancy terms like business logic. These terms are common to all application development and have a common definition regardless of technology. You have a point though, that implementation differs from technology to technology.

    To be sure we all are on the same page Ill quote fowler on what business logic is and what data access is.

    Quote Originally Posted by Martin Fowler - POEAA pg.20
    Data source logic is about communicating with other systems that carry out tasks on behalf of the application. ... the biggest piece of data source logic is a database ...

    Domain Logic, also referred to as business logic. This is the work that this application needs to do for the domain youre working with. It involves calculations based on inputs and stored data, validation of any data that comes in from the presentation, and figuring out exactly what data source logic to dispatch, depending on commands received from the presentation.
    Quote Originally Posted by arborint
    Putting them in separate classes and putting a shared mapper in between as above may achieve less separation than a simple DAO because the mapper becomes brittle.
    Maybe I should clarify what is going on in the above code. The PHPDataMapper class is using Object-Relational Metadata Mapping. I havent made a seperate "data access" class for each object and then put a shared mapper in between. I only have to describe the relationship between object and database, and then do any* query I want on it using the $pdm object.

    If you still think this is more brittle, please explain.

    *I havent implemented many-to-many etc relationships yet. Still thinking out the best way to do this in php.

  8. #8
    SitePoint Zealot sike's Avatar
    Join Date
    Oct 2002
    Posts
    174
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Brenden Vickery
    Maybe I should clarify what is going on in the above code. The PHPDataMapper class is using Object-Relational Metadata Mapping. I havent made a seperate "data access" class for each object and then put a shared mapper in between. I only have to describe the relationship between object and database, and then do any* query I want on it using the $pdm object.

    If you still think this is more brittle, please explain.

    *I havent implemented many-to-many etc relationships yet. Still thinking out the best way to do this in php.
    i am using nearly the same setup as brendan for my businessobjects. i have a single class (i named it gateway) which is able to perform (nearly) any query on my bos. it uses meta descriptions of the structure to build the necessary joins. a short example :
    PHP Code:
       /*
       * Adding Article
       */
       
    $author BusinessObjectGateway::addNew('Author');
       
    $author->name 'bar';
       
    $article BusinessObjectGateway::addNew('Article');
       
    $article->setAuthor($author);
       
    $article->date time();
       
    $article->title 'foo';
       
    $article->save(); // will persist article and author
     
       /*
       * Query Article
       */
       
    $article BusinessObjectGateway::findOne('Article'"id = 1");
     
       
    /*
       * Query Articles
       */
       
    $articles BusinessObjectGateway::findMany('Article'"author.name = 'bar'");
       foreach(
    $articles as $article)
       {
       } 
    Sike

    ps. many-to-many relations give me headaches too

  9. #9
    SitePoint Addict
    Join Date
    May 2003
    Location
    Calgary, Alberta, Canada
    Posts
    275
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by lazy_yogi
    What got me to thinking about this was a link to nakedobjects (http://www.nakedobjectsorg/content.html)- which points out that a logical entity should really be an entity. It can have agregate objects to help it do its job/s, but it should be *the* place of contact for all things relating to that entity - with all the processing hidden inside it. This idea really makes sense to me.
    I got a chance to quickly skim over the naked objects stuff and it seems like its aimed specifically towards administration "CRUD" type tasks and has alot to do with the UI.

    I couldnt really find much about how the Naked Object framework does data access but this quote shows they recommend a mapper in some cases anyway.
    Quote Originally Posted by http://www.nakedobjects.org/downloads.html
    The SQL Object Store provides a link to a relational database via JDBC. At present this object store will automatically create a database schema and map the objects to it. If you already have tables and they have a one-to-one correspondence with the business objects then these can be mapped in the configuration file. Complicated mappings, however, must be done by writing a mapper class.
    I dont think they are advocating moving every possible thing that a "Customer" could do into a Customer class but to aggregate these objects and have a single interface for commands like you said.

    On top of moving the datasource interface into the Customer class they are also suggesting you move the controller interface into the Customer class.

    Im not sure I like this idea too much.

  10. #10
    SitePoint Guru
    Join Date
    Dec 2003
    Location
    oz
    Posts
    819
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by http://www.nakedobjects.org/downloads.html
    The SQL Object Store provides a link to a relational database via JDBC. At present this object store will automatically create a database schema and map the objects to it. If you already have tables and they have a one-to-one correspondence with the business objects then these can be mapped in the configuration file. Complicated mappings, however, must be done by writing a mapper class.
    Most of what I do in information systems is CRUD, not complicated mappings - which I why I think a mapper in my case is unnecessary.

  11. #11
    SitePoint Addict
    Join Date
    Apr 2004
    Location
    Melbourne
    Posts
    362
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    http://tanus.dotgeek.org/?viewObject=28

    My take on it is to have a business objects as the child of a datamapping object. This datamapping object loads all of the properties (accessible by overloading __get and __set methods) for a certain classtype by reading from the database what properties it has access to.

    For example, the parent EntityObject does basic things like

    Code:
    private $properties;
    private $allowedProperties;
    
    function __construct($classtype)
    { 
        Load from the database property table the properties this classtype can have.
        foreach result
            add the property to the allowedproperties list.
            create a property object which stores the value of this property (this is updated when a __set call is made, and retrieved on a __get call)
    }
    
    function SaveObject()
    {
        if a new object, save it to the database 'class' table, and retrieve its id.
        if a restored object, update its settings (name, path)
        
        iterate through property objects and save them to the 'propertyvalue' table
    }
    
    
    function LoadObject(id)
    {
         loads the specific values for an object from the 'class' table. 
         iterates through this object's properties and loads them from the propertyvalue table
        // performs permiission checks on the object to make sure the current user has access to it (uses unix style permission triplet). Returns false otherwise.
    }
    So the definition for an 'entityfolder' object looks like http://tanus.dotgeek.org/images/xml.png
    This is enough to be able to create an entityobject with all the properties of an 'entityfolder' class and be able to access them as $object->foldername = 'name'; However, to add business logic, I create an actual entityfolder class which extends entityobject;

    Code:
    class EntityFolder extends EntityObject
    {
        // all the business logic for this class goes here. The only interface this 
        // object might have called on it from the parent that relate directly to db usage 
        // is SaveObject(), which writes it.
    }
    
    
    // to load an object, a utility class takes care of loading the type, then loading the object
    
    public function LoadObjectById($id) {
        // get the type of the class this id represents
        $type = self::GetTypeFor($id);
        // Create an object of that type
        $obj = new $type();
        $obj->LoadObject($id);
        
        return $obj;
    }
    It makes creating database aware objects VERY easy (XML definitions!), and simplifies the database access needed to perform most things. I'll be open sourcing it soon, once I get the admin side of things cleaned up a bit.

  12. #12
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I agree that many of the implementations listed here are reasonable solutions. They are a style of coding and may achieve separation of program logic or not.

    As to my comment about terms, I think using terms like Data source logic and Domain Logic can obscure more than clarify.

    In PHP the implementations above end up looking more like SQL generators than "business objects." And, they can become brittle when LoadObjectById($id) or findMany('Article', "author.name = 'bar'") are not sufficient as the complexity of the eode and the SQL increases.

  13. #13
    SitePoint Addict
    Join Date
    May 2003
    Location
    Calgary, Alberta, Canada
    Posts
    275
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by arborint
    In PHP the implementations above end up looking more like SQL generators than "business objects." And, they can become brittle when LoadObjectById($id) or findMany('Article', "author.name = 'bar'") are not sufficient as the complexity of the eode and the SQL increases.
    They arent business objects, they're data source objects. SQL generation is one part of what they do.

    A problem I have had with using a active record or mapper that is hand coded is that it becomes very unwieldily.

    Lets say we have a active record with this interface:
    PHP Code:
    class Customer {
        
    // all setters / getters
        
    function findAll();
        function 
    findAllWithLastNameLike($lastname);
        function 
    findById($id);

    Then when we are optimising SQL we find that we use findAllWithLastNameLike on 3 pages. One where we need all the customers data, one where we only need the firstname and lastname and one where we need the firstname, lastname and id. So we change the interface to look like this:
    PHP Code:
    class Customer {
        
    // all setters / getters
        
    function findAll();
        function 
    findAllWithLastNameLike($lastname);
        function 
    findAllWithLastNameLikeOnlyFirstLast($lastname);
        function 
    findAllWithLastNameLikeOnlyFirstLastId($lastname);
        function 
    findById($id);

    In a large application when you factor in different variations in the optimised SQL including limits, ordering etc you usually end up with a large amount of functions that get used only on one page in a very specific way. You now have huge business objects that take into account every different way you need to find themselves from the data source.

    If you build a ORMapping solution that does handle every situation you never have to worry about what the database looks like except when you descibe the relationship, which might be as simple as:
    PHP Code:
    <class name="Team" table="TEAM">
           <
    field name="id" column="TEAM_ID" />
           <
    field name="name" column="TEAM_NAME" />
           <
    field name="city" column="TEAM_CITY" />
       </class>
       <class 
    name="Player" table="PLAYER">
           <
    field name="id" column="PLAYER_ID" />
           <
    field name="name" column="PLAYER_NAME" />
           <
    field name="team" column="PLAYER_TEAM_ID">
              <
    join class="Team" field="id" />
           </
    field>
       </class> 
    You get to focus on the business objects and their relationship to each other instead of the database tables relationships.

    In a complete solution you can do very complex queries using 1:*, *:*, *:1, 1:1, bidirectional, ordering, limits, grouping etc using a very simple domain oriented interface.

    So to find all the teams and its players who are in Dallas limited to 20 results offset by 20, orderd by team name we might do this:
    PHP Code:
    $query $pdm->newQuery('Team','Team.city == ?');
    $query->setRange(2040);
    $query->setOrdering('Team.name ascending');
    $teams $query->execute('Dallas');
    foreach(
    $teams as $team) {
       echo 
    $team->getName();
       foreach(
    $team->getPlayers as $player) {
          echo 
    $player->getName();
       }


  14. #14
    SitePoint Addict
    Join Date
    Oct 2004
    Location
    Sutton, Surrey
    Posts
    259
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Brenden Vickery
    A problem I have had with using a active record or mapper that is hand coded is that it becomes very unwieldily.

    Lets say we have a active record with this interface:
    PHP Code:
    class Customer {
        
    // all setters / getters
        
    function findAll();
        function 
    findAllWithLastNameLike($lastname);
        function 
    findById($id);

    Then when we are optimising SQL we find that we use findAllWithLastNameLike on 3 pages. One where we need all the customers data, one where we only need the firstname and lastname and one where we need the firstname, lastname and id. So we change the interface to look like this:
    PHP Code:
    class Customer {
        
    // all setters / getters
        
    function findAll();
        function 
    findAllWithLastNameLike($lastname);
        function 
    findAllWithLastNameLikeOnlyFirstLast($lastname);
        function 
    findAllWithLastNameLikeOnlyFirstLastId($lastname);
        function 
    findById($id);

    Whenever I see code like this I have to ask the question "Why are you guys making life difficult for yourselves?" Rather than trying to hard-code a separate function that will deal with a specific set of selection criteria why don't you adopt a generic approach with a single
    PHP Code:
    $fieldarray $object->getData($where); 
    function. The $where argument is a string that can be used as the WHERE clause in an sql SELECT statement, so it can contain as many or as few parts as you wish. So regardless of what you want inside your WHERE clause you never have to create an alternative to the getdata() method.

    By default this will select all columns from the current table, but if you want something different then supply a value such as:
    PHP Code:
    $object->sql_select 'column1, column2'
    When it comes to dataMappers - I don't use them. Basic information is held within each business object, but passed to a DAO when communication with the physical database is required.

    When it comes to data access objects - I have one for the whole application, not one per database table. How is this possible? Because my DAO is not coded with any database, table or column names. These are all supplied by the calling object at run time. The DAO will then use whatever information it is given to construct an sql query, then call the relevant API for its designated DBMS.

    Note that I have a separate DAO for each DBMS, so I can switch from one DBMS to another simply by switching my DAO.

  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 Tony Marston
    Whenever I see code like this I have to ask the question "Why are you guys making life difficult for yourselves?"
    There are plenty of occasions where this is easier. Either the methods wrap very simple solutions, so that hand coding is sufficient, or they mask a complex problem such as non-SQL data, non-isomorphic schema (e.g. group bys), highly optimised queries or non normalised data. They are just the ones I can think of off the top of my head. I bet there are more.

    Quote Originally Posted by Tony Marston
    Rather than trying to hard-code a separate function that will deal with a specific set of selection criteria why don't you adopt a generic approach
    Yes.

    Quote Originally Posted by Tony Marston
    with a single
    PHP Code:
    $fieldarray $object->getData($where); 
    function.
    No. The column names, and with it the schema, are bleeding upwards and destroying the layering. A minor concern for one object for each row, a big headache for fetching collections and other relationships (e.g. ForeignKeyMapping). It's also harder to unit test as a result.

    A DataMapper is a full on solution and one not to be taked lightly. Not least because you will need a new class for every business object to do the mapping. If you want the most flexible solution (it offers complete data source independence), and you are willing to resort to tricks like reflection and code generation to speed up development, then it is the Rolls-Royce answer.

    Depending on the problem there could be much simpler answers, but the discussion seems to be too abstract right now for me to say.

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

  16. #16
    SitePoint Addict
    Join Date
    Oct 2004
    Location
    Sutton, Surrey
    Posts
    259
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by lastcraft
    There are plenty of occasions where this is easier. Either the methods wrap very simple solutions, so that hand coding is sufficient, or they mask a complex problem such as non-SQL data, non-isomorphic schema (e.g. group bys), highly optimised queries or non normalised data. They are just the ones I can think of off the top of my head. I bet there are more.
    I do not think it is easier to code additional functions for different types of SELECT if your infrastructure already contains a single generic mechanism that will already cater for all possibilities. As to customising the SELECT statement for particular situations I solved that problem several years ago. If you look at the sql manual you will see that the SELECT statement has several clauses. If you define each of these fragments as a separate variable within the class these can be assembled by the DAO into a complete query, as in:
    PHP Code:
    $query "SELECT $select_str FROM $from_str $where_str $group_str $having_str $sort_str $limit_str"
    The presentation layer component (page controller) may supply whatever values for each fragment it wishes to the business layer component (business object) which may or may not adjust the values before passing them to the DAO for final assembly and execution.

    Quote Originally Posted by lastcraft
    Quote Originally Posted by TonyMarston
    ... with a single function:
    PHP Code:
    $fieldarray $object->getData($where); 
    No. The column names, and with it the schema, are bleeding upwards and destroying the layering. A minor concern for one object for each row, a big headache for fetching collections and other relationships (e.g. ForeignKeyMapping). It's also harder to unit test as a result.
    I do not recognise this concept called "bleeding upwards", so I ignore it. It certainly does not destroy my layering as my infrastructure is based on the 3-Tier architecture which I have successfully implemented in more than one language. Each layer (or tier) does exactly what it is supposed to, so there is no problem with any sort of bleeding.

    Quote Originally Posted by lastcraft
    A DataMapper is a full on solution and one not to be taken lightly. Not least because you will need a new class for every business object to do the mapping. If you want the most flexible solution (it offers complete data source independence), and you are willing to resort to tricks like reflection and code generation to speed up development, then it is the Rolls-Royce answer.
    I do not need a separate dataMapper to go with each business object as I successfully combine the two into a single class/object. This keeps all the informaton about an entity (including information on its data structure) in a single class which is what encapsulation is supposed to be about.

    Before you jump on me and say that I am not achieving the "correct separation of concerns" let me point out that although each business object holds information about the data structure it does nothing with it - it is passed to the DAO which is the only object in the entire application which communicates with the database. Thus my business object holds all information about an entity (thus satisfying the principle of encapsulation), but it is the DAO which is responsible for using this information to communicate with the database. This is my interpretation of what "separation of responsibilities" actually means.

  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 Tony Marston
    Whenever I see code like this I have to ask the question "Why are you guys making life difficult for yourselves?" Rather than trying to hard-code a separate function that will deal with a specific set of selection criteria why don't you adopt a generic approach with a single
    PHP Code:
    $fieldarray $object->getData($where); 
    function. The $where argument is a string that can be used as the WHERE clause in an sql SELECT statement, so it can contain as many or as few parts as you wish. So regardless of what you want inside your WHERE clause you never have to create an alternative to the getdata() method.
    I don't think that's making life more difficult

    Here's what I see as disadvantages of your solution

    a) with a query object/criteria approach joins are generated as needed by the mapper, I don't see how you would add a join only within a where clause

    b) maybe you don't want to hardcode your query but generate it based on certain conditions, criteria objects can easily be generated but with valid SQL that's more difficult

    c) maybe you want an instance of a certain persistable object as result fo your query, not just an array of fields.

  18. #18
    SitePoint Addict
    Join Date
    Oct 2004
    Location
    Sutton, Surrey
    Posts
    259
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by hantu
    Here's what I see as disadvantages of your solution

    a) with a query object/criteria approach joins are generated as needed by the mapper, I don't see how you would add a join only within a where clause
    See my previous reply in which I indicate how the sql fragments are defined within the presentation layer before they are passed through the business layer, then to the DAO for final assembly and execution. All without a dataMapper.

    Quote Originally Posted by hantu
    b) maybe you don't want to hardcode your query but generate it based on certain conditions, criteria objects can easily be generated but with valid SQL that's more difficult
    Conditions may be hard-coded, or they may be supplied at run time from user input, or they may be inserted into the sql fragment by code within the business object. All these variations still do not require more than a single generic getdata() method (at least in my infrastructure they don't).

    Quote Originally Posted by hantu
    c) maybe you want an instance of a certain persistable object as result fo your query, not just an array of fields.
    Maybe, but the point under discussion is creating functions to retrieve data under different circumstances. Doing a retrieve without expecting data to be returned is another topic.

  19. #19
    SitePoint Addict
    Join Date
    May 2003
    Location
    Calgary, Alberta, Canada
    Posts
    275
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Tony Marston
    Whenever I see code like this I have to ask the question "Why are you guys making life difficult for yourselves?" Rather than trying to hard-code a separate function that will deal with a specific set of selection criteria why don't you adopt a generic approach with a single
    PHP Code:
    $fieldarray $object->getData($where); 
    function. The $where argument is a string that can be used as the WHERE clause in an sql SELECT statement, so it can contain as many or as few parts as you wish. So regardless of what you want inside your WHERE clause you never have to create an alternative to the getdata() method.
    ...
    Hey Tony,

    I think you've missed the point. "Hard-coded" behind that function may be a generic approach similar to yours. The point is, it doesnt matter as long as the parameters to the function are the same and the the return value is what is expected.

    By putting the generic approach inside the function you start to see spots to refactor and your code becomes cleaner.

    Quote Originally Posted by Tony Marston
    I do not recognise this concept called "bleeding upwards", so I ignore it.

  20. #20
    SitePoint Enthusiast
    Join Date
    Apr 2004
    Location
    US
    Posts
    51
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Brenden Vickery
    PHP Code:
    $query $pdm->newQuery('Team','Team.city == ?');
    $query->setRange(2040);
    $query->setOrdering('Team.name ascending');
    $teams $query->execute('Dallas');
    foreach(
    $teams as $team) {
       echo 
    $team->getName();
       foreach(
    $team->getPlayers as $player) {
          echo 
    $player->getName();
       }

    What if you want to get the total number of Team (where xxx condition), then how would you attack that problem?

    Can you show more how the PDM is actually working behind the scene?

    Thanks

  21. #21
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    A couple of points. First, I have no problem with "data source objects" as you describe them, not that they tend to grow a number of query specific functions. It should be noted that in PHP it often makes sense to split up these objects so they are focused on specific areas or pages of the applilcation. This is different than a traditional linked executable where the object is usually resident with the code calling it.

    A PHP application is "linked" for each request so a different kind of care needs to be taken in the design. This is where many of the traditional design theories fall short when implemented in PHP for web apps.

    Likewise I think there is a valid question whether the complexity of maintaining OR Mapping XML and code are worth the trouble in PHP. Again, in traditional applications you can have large objects and maps where it makes sense.

    I'd be interested in what you think the decision points are for deciding whether to use the simpler DAO approach or move to a ORMapper?

  22. #22
    SitePoint Addict
    Join Date
    May 2003
    Location
    Calgary, Alberta, Canada
    Posts
    275
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by arborint
    I'd be interested in what you think the decision points are for deciding whether to use the simpler DAO approach or move to a ORMapper?
    For me, I would go with the ORMapper first everytime. When optimizing if you find a page that is slow and have found the problem to be the ORMapper (Not the sql) then I would move to a DAO or similar. If there is still a problem go with straight mysql_query($conn, $sql);. Ive never run into this situation in the short time Ive been using an ORMapper but this would be my strategy.

    Quote Originally Posted by arborint
    Likewise I think there is a valid question whether the complexity of maintaining OR Mapping XML and code are worth the trouble in PHP. Again, in traditional applications you can have large objects and maps where it makes sense.
    Ive found that I spend much less time maintaining the XML than I would have spent writing SQL and mapping objects to objects (everything invloved in hand coding a datasource object). I have spent a heck of alot of time writing the ORMapper and its not a complete solution yet but there are open-source products like Propel that probably do a better job.

    My number 1 goal is to get a product to the customer as quick as possible with the least bugs possible. Using an ORMapper has decreased the time needed by a good amount.

    The downside I was expecting was that using the ORMapper would increase load considerably but this hasnt been a problem... yet anyway.

  23. #23
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I'd be interested to see more how your code works.

    My experience is that "less time maintaining the XML ... than SQL" plus "a heck of alot of time writing the ORMapper and its not a complete solution yet" equals about the same amount of time both ways but more code to maintain for the mapper. I have also noticed the performance hit of loading the map data on high traffic sites.

  24. #24
    SitePoint Addict
    Join Date
    May 2003
    Location
    Calgary, Alberta, Canada
    Posts
    275
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Youre right, at this point I may not have saved any time but I figure Im investing in the future. Once the ORMapper is done I wont have to do anything to it and Ill be saving lots of time.

    Currently Im porting the code to php 5 and doing some re-writing. Here's an early version of the mappings (no joins, types).

    PHP Code:
    class ObjectRelationMap {
        private 
    $doc;
        private 
    $classMaps = array();
        public function 
    __construct($map) {
            if (
    file_exists($map)) {
                
    $this->doc DOMDocument::load($map);
            } else {
                
    $this->doc DOMDocument::loadXML($map);
            } 
        } 
        public function 
    getClassMap($className) {
            if (!
    array_key_exists($className$this->classMaps)) {
                
    $xp = new DomXPath($this->doc);
                
    $nodeList $xp->query(
                    
    "//*[@name = '" $className "']");
                if (
    $nodeList->length == 0) {
                    throw 
    SQLException();
                } 
                
    $classMap = new ClassMap(
                    
    $nodeList->item(0)->getAttribute('name'),
                    
    $nodeList->item(0)->getAttribute('table'));
                
    $this->getFieldMaps($classMap$nodeList->item(0));
                
    $this->classMaps[$className] = $classMap;
            } 
            return 
    $this->classMaps[$className];
        } 
        private function 
    getFieldMaps($classMap$domElement) {
            foreach(
    $domElement->childNodes as $node) {
                if (
    $node instanceof DomElement) {
                    
    $classMap->addField(
                        
    $node->getAttribute('name'),
                        
    $node->getAttribute('column'));
                } 
            } 
        } 

    class 
    ClassMap {
        private 
    $tableName;
        private 
    $className;
        private 
    $fields = array();
        public function 
    __construct($className$tableName) {
            
    $this->className $className;
            
    $this->tableName $tableName;
        } 
        public function 
    addField($fieldName$columnName) {
            
    $this->fields[$fieldName] = new FieldMap(
                
    $fieldName,
                
    $columnName);
        } 
        public function 
    getClassName() {
            return 
    $this->className;
        } 
        public function 
    getTableName() {
            return 
    $this->tableName;
        } 
        public function 
    getFields() {
            return 
    $this->fields;
        } 

    class 
    FieldMap {
        private 
    $fieldName;
        private 
    $columnName;
        public function 
    __construct($fieldName$columnName) {
            
    $this->fieldName $fieldName;
            
    $this->columnName $columnName;
        } 
        public function 
    getFieldName() {
            return 
    $this->fieldName;
        } 
        public function 
    getColumnName() {
            return 
    $this->columnName;
        } 

    I havent done any testing on the effect loading the xml has but the alternative is to not read any xml and just build the maps by hand. Even in a situation where you have 40-50 object-relations its no different than building 50 customer objects without having to go to a database.

    PHP Code:
    class ObjectRelationMap {
        private 
    $classMaps = array();
        public function 
    __construct() {
            
    $classMap = new ClassMap('Team''TEAM');
            
    $classMap->addField('id''TEAM_ID');
            
    $classMap->addField('name''TEAM_NAME');
            
    $classMap->addField('city''TEAM_CITY');
            
    $this->classMaps['Team'] = $classMap;

            
    $classMap = new ClassMap('Player''PLAYER');
            
    $classMap->addField('id''PLAYER_ID');
            
    $classMap->addField('name''PLAYER_NAME');
            
    $classMap->addField('team''PLAYER_TEAM_ID');
            
    $this->classMaps['Player'] = $classMap;
        } 
        public function 
    getClassMap($className) {
            if (
    array_key_exists($className$this->classMaps)) {
                return 
    $this->classMaps[$className];
            } else {
                throw 
    SQLException();
            } 
        } 


  25. #25
    SitePoint Zealot
    Join Date
    Jul 2004
    Location
    Brazil,Maringá-PR
    Posts
    128
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    First:
    I would like to say that the discussions on this forums are the best Iīv ever seen. Does anybody knows other forums that has design level discussions like this one?

    Second:
    ORMapper, Data source logic and Domain Logic are new terms to me. Where can I learn more about?

    Now, letīs rock...
    Brenden Vickery, I donīt like this "findAllWithLastNameLikeOnlyFirstLastId". I think itīs like the Ant-Pattern God Object. Your object will become a repository of procedures.
    I donīt like this too:
    PHP Code:
    $query $pdm->newQuery('Team','Team.city == ?'); 
    $query->setRange(2040); 
    $query->setOrdering('Team.name ascending'); 
    $teams $query->execute('Dallas'); 
    For me itīs just a sql builder. The same code is clear to read doing this:
    "SELECT * FROM Team WHERE Team.city == 'Dallas' ORDER BY Team.name ASC LIMIT 20, 40"
    The only reason to do this is if this class interface many databases drivers.

    sike
    Your solution for me is the more plausive. Your code didnīt say its data is coming from a database, it could be a simple text file or an array dump. IMHO Itīs the best abstraction but itīs the less seen in the forums world around. If possible, I would like to discuss more about this one.


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
  •