SitePoint Sponsor

User Tag List

Results 1 to 11 of 11
  1. #1
    SitePoint Enthusiast
    Join Date
    Jan 2009
    Posts
    81
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    MVC (& frameworks) - how do you structure your Model class?

    For the past few months I've been working on and simultaneously using my own MVC framework. IMO, it's simple to use yet powerful (although which framework author wouldn't say the same thing? ), especially because of my Model class. Here's an example of how to create a new row in the Posts table:
    PHP Code:
    $post $this->loadModel('Post'); // Pass an ID as the 2nd param if you want to edit an existing row
    $post->setField('author''Steven');
    $post->setField('title''Some fake title');
    $post->setField('body''Lorem ipsum');
    $post->save(); 
    Every time setField() is called, an internal array (to the Model class) has a key => value pair added. The key is the name of the field (first param of setField()). Then when save() is called, the SQL statement is automatically generated.
    Naturally, if you setField() on a field that doesn't exist in the DB, the query would fail.

    Now today I was looking at the Recess framework (http://www.recessframework.org/) and noticed how he has his Model classes set up. You use an install tool that creates the Model classes automatically once you define the database structure (using the same tool). Within each class there would be properties for each field in the DB row. I'm trying to see if there's an advantage to having the fields hardcoded in the class, because I think I've seen other frameworks do the same thing. I imagine you'd still have to have a method similar to my setField() method, or you would just reference the property directly ($post->title = 'Some fake title'). Plus, if you ever update the DB table, you need to remember to go and edit the class to contain the new field as a property. A possible advantage to this method is that you could double check that you're only settings fields that exist by looking within the class before sending the SQL statement, but IMO that's a very small advantage.

    What are your thoughts on this? I'm certainly no expert, nor pretend to be one, but I really think Recess' method is overkill. Although if I'm wrong and there are advantages I'd love to hear them
    Last edited by Arrrms; Mar 25, 2009 at 18:43.

  2. #2
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,182
    Mentioned
    16 Post(s)
    Tagged
    4 Thread(s)
    That may work for a insert, but how does your find operation function?

    Do I need to define each field when retrieving data? If so that could potentially be a lot of extra that code that could be eliminated by simple storing the structure in the class.

    Arrrms wrote
    A possible advantage to this method is that you could double check that you're only settings fields that exist by looking within the class before sending the SQL statement, but IMO that's a very small advantage.
    Well normally all other properties(overloaded) are ignored besides those that belong directly to the model. Inserting data is essentially just running a loop on the fields for the model and running the appropriate validation to create a insert statement.

    Arrrms wrote
    Naturally, if you setField() on a field that doesn't exist in the DB, the query would fail.
    How do you know the field doesn't exist in the database table if your not storing the actually field names anywhere may I ask? If you are running a query every time the setField() method is called that seems like highly flawed and unnecessary overhead.

    Your method seems ok if your talking just a insert, but it seems highly inconvenient for a find operation.

  3. #3
    SitePoint Enthusiast
    Join Date
    Jan 2009
    Posts
    81
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    The above code works for both INSERTS and UPDATES

    I have three methods of retrieving results:
    1) query() - lowest level possible, provide it with pure SQL and it'll return a custom DBResult object. I only use this when I have to write complicated JOIN queries.

    2) get() - accepts an array of options that supports 'where', 'limit', 'page', 'orderBy' (and others). example usage:
    PHP Code:
    $post $this->loadModel('Post');
    $posts $post->get(array('where' => 'author = Steven''limit' => '0,5''orderBy' => 'id DESC')); 
    3) a magic 'findBy' method that uses __call().
    example usage:
    PHP Code:
    $posts $this->loadModel('Post')->findByAuthor(array('Steven')); 
    It takes an optional 2nd param that lets you modify the query
    PHP Code:
    $posts $this->loadModel('Post')->findByAuthor(array('Steven''ORDER BY id DESC')); 
    Now to work with the data:
    PHP Code:
    echo $post->getField('author');
    echo 
    $post->getField('title'); 
    There's also a getFields() method would returns an assoc. array of all the fields

    Is that what you meant when you asked "Do I need to define each field when retrieving data?"?

  4. #4
    SitePoint Enthusiast
    Join Date
    Jan 2009
    Posts
    81
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Just noticed your edits after I responded.


    Quote Originally Posted by oddz View Post
    Arrrms wrote


    Well normally all other properties(overloaded) are ignored besides those that belong directly to the model. Inserting data is essentially just running a loop on the fields for the model and running the appropriate validation to create a insert statement.

    Arrrms wrote


    How do you know the field doesn't exist in the database table if your not storing the actually field names anywhere may I ask? If you are running a query every time the setField() method is called that seems like highly flawed and unnecessary overhead.

    Your method seems ok if your talking just a insert, but it seems highly inconvenient for a find operation.
    I wouldn't know ahead of time if the field doesn't exist in the table. I would only know by checking the error log when the query fails. But I think the only time a bad field name would be provided is during development, and as such it's up to the developer (me) to ensure that the proper field names are being used.

    I'm not running a query every time setField() is called - only when save() is called.

    When you perform find operations, what normally happens in the background? I'm interested to see how hardcoding the fields could aid this.

  5. #5
    SitePoint Wizard
    Join Date
    Mar 2008
    Posts
    1,149
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I personally like entirely splitting the database logic completely from the actual object in question, meaning that each modal is its own independent class with its own fields and such. I am a big fan of separation, because it encourages high code reuse.

    I don't have anything for PHP, but I do like SQLAlchemy of Python:
    http://www.sqlalchemy.org
    It's a very advanced ORM package that easily supports automatic joins and advanced querying (without having to write too much manual SQL). You map the database to these modals. Unfortunately, it uses a lot of syntactic sugar that is only possible in Python. But I like the concept.

  6. #6
    SitePoint Enthusiast
    Join Date
    Jan 2009
    Posts
    81
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I should note that I don't know the first thing about setting up relationships between models in one class. In one of Recess' videos he shows how he links the Post Model to the Comment Model. To display a post and it's comments I simply instantiate a Post Model object and a separate Comment Model object. The full code would look like this:
    PHP Code:
    $post $this->loadModel('Post'7);
    $commentModel $this->loadModel('Comment');
    $comments $commentModel->findByPostID($post->getField('id')); 
    Edit: The above code would be in my Posts controller. Then I would send the $post and $comments variables to a view.

  7. #7
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,182
    Mentioned
    16 Post(s)
    Tagged
    4 Thread(s)
    Well the problem with the finder seems to be that your selecting all (*) data from the table without specifying which fields you need. Is this the case? Obviously the evil star could be avoided by specifying the fields that pertain to the model.

    Seems like what you have is a Gateway with flawed logic in that entities should be separate from the Gateway.

    $gateway->setField('title','whatever');

    That seems flawed in that the gateway is both responsible for storing and moving data between the domain and database. The more the common and loose approach would be to separate out the two responsibilities:

    gateway: move data from database to domain and vice versa // logic
    entity: store data // no logic

    Ex.

    // entity contains no business logic
    $post = new PostEntity();
    $post->setField('title','blah');
    $post->setField('created',time());

    // gateway contains all business logic and moves data from domain to database and vice-versa
    // yet it doesn't store any row data
    $postGateway = new PostGateway($db);
    $postGateway->save($post);

    Although, if the system works for you then by all means use it. I;m just going by what is common and thought to be "correct" approach.

    Personally I prefer ActiveRecord over the Gateway approach becasue I find much more easier to use. Instead of always needing to create two objects all I need is one. Yes, that comes at a expense(php), but I feel its worth it.

  8. #8
    SitePoint Enthusiast
    Join Date
    Jan 2009
    Posts
    81
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    So far it seems to be working fine, but this is my first real framework, and I've only been coding OOP PHP for ~ 5 months. Add to that the fact that I'm a tinkerer, and I'm always looking to see if my code can be improved

    I can pass a 'fields' option to my get() method to avoid fetching all the fields.
    $posts = $postModel->get('fields' => 'title, author, body');

    I'm not really sure what you mean about the gateway/domain. Seems like you're suggesting one class to fetch data from the DB (is this the 'gateway'?) and store it in a model class (is this the 'entity'?), and then interacting with the model class to use the data (for output or processing). How then would I save the model/entity? Would it have an internal method(s) to interact with the gateway?

  9. #9
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,182
    Mentioned
    16 Post(s)
    Tagged
    4 Thread(s)
    Yeah, when it comes to supporting complex hierarchies you need to think and approach the problem much differently then if your only dealing with one model. You would be hard pressed to write something that doesn't support relationships in the beginning and adding it later. In my experience almost everything needs to be handled differently.

    Arrms wrote:
    $post = $this->loadModel('Post', 7);
    $commentModel = $this->loadModel('Comment');
    $comments = $commentModel->findByPostID($post->getField('id'));
    I'm not certain if you have seen my post(s) in your other thread, but it is possible to essentially do all that in just one query and one line.

    Code:
    $post = Post::find(
      'one'
      ,array(
          'include'=>'comments'
          ,'id'=>7
      )
      ,array(
         'require'=>false
      )
    );
    What you essentially get back is a post object with a comments property that is a array of all that posts comments.

    Code:
    echo '<h3>',$post->title,'</h3>';
    
    if($post->comments) {
      foreach($post->comments as $comment) {
        echo '<p>',$comment->message,'</p>';
      }
    }
    Furthermore, I have yet to come across a system that allows this you could further bring in the user for the post and each message.

    Code:
    $post = Post::find(
      'one'
      ,array(
          'include'=>array('comments','user')
          ,'id'=>7
      )
      ,array(
         'require'=>false
         ,'include'=>'user'
      )
      ,array(
        'require'=>false
      )
    );
    Then we could do:

    Code:
    echo '<h3>',$post->title,'</h3>';
    echo '<p>',$post->user->name,'</p>';
    
    if($post->comments) {
      foreach($post->comments as $comment) {
        echo '<p>',$comment->message,'</p>';
        echo '<p>',$comment->user->name,'</p>';
      }
    }
    Managing one table is relatively simple. However, most the time you need to manage more then one and that is where everything becomes tricky.

    Another way you could approach the previous example if you need pagination for the comments is to use the magical __call after bringing in the post.

    Code:
    $post = Post::find('one',array('id'=>7,'limit'=>1,'include'=>'user'));
    $comments = $post->getComments(array('limit'=>20,'offset'=>19,'include'=>'user'));

  10. #10
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,182
    Mentioned
    16 Post(s)
    Tagged
    4 Thread(s)
    Arrms wrote:
    I'm not really sure what you mean about the gateway/domain. Seems like you're suggesting one class to fetch data from the DB (is this the 'gateway'?) and store it in a model class (is this the 'entity'?), and then interacting with the model class to use the data (for output or processing). How then would I save the model/entity? Would it have an internal method(s) to interact with the gateway?
    Yes, the gateway essentially moves data from the domain to the database(insert,update,destroy) and database to domain(find).

    The entity is normally just a glorified array essentially that you can add properties. The Gateway returns loaded entities or you may pass them into the gateway to save them to the database. I haven't built many generic gateways. It seems like it more trouble then what its worth. Although if your hard coding the logic a gateway it the way to go although this normally results in tedious code. I'm more interested in making generic things I can reuse that work in almost any circumstance.

  11. #11
    SitePoint Enthusiast
    Join Date
    Jan 2009
    Posts
    81
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I think I just found a major advantage to hard coding the fields in each model class: being able to validate the data being sent to each field before sending it to the DB. If my model has a 'zipCode' property, I should be able to check that it's numeric and between 5 and 9 digits before sending it to the DB.

    I think my next step is looking into Doctrine and Propel and seeing if I can fit either into my existing set up.

    edit: Meant to write that I can check the validity of the fields automatically by checking against a schema.


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
  •