SitePoint Sponsor |
|
User Tag List
Results 1 to 11 of 11
-
Mar 25, 2009, 18:12 #1
- 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();
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 themLast edited by Arrrms; Mar 25, 2009 at 18:43.
-
Mar 25, 2009, 18:46 #2
- Join Date
- Jul 2006
- Location
- Augusta, Georgia, United States
- Posts
- 4,194
- Mentioned
- 17 Post(s)
- Tagged
- 5 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.
Arrrms wrote
Naturally, if you setField() on a field that doesn't exist in the DB, the query would fail.
Your method seems ok if your talking just a insert, but it seems highly inconvenient for a find operation.
-
Mar 25, 2009, 18:56 #3
- 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'));
example usage:
PHP Code:$posts = $this->loadModel('Post')->findByAuthor(array('Steven'));
PHP Code:$posts = $this->loadModel('Post')->findByAuthor(array('Steven', 'ORDER BY id DESC'));
PHP Code:echo $post->getField('author');
echo $post->getField('title');
Is that what you meant when you asked "Do I need to define each field when retrieving data?"?
-
Mar 25, 2009, 18:59 #4
- Join Date
- Jan 2009
- Posts
- 81
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Just noticed your edits after I responded.
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.
-
Mar 25, 2009, 19:01 #5
- 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.
-
Mar 25, 2009, 19:05 #6
- 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'));
-
Mar 25, 2009, 19:14 #7
- Join Date
- Jul 2006
- Location
- Augusta, Georgia, United States
- Posts
- 4,194
- Mentioned
- 17 Post(s)
- Tagged
- 5 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.
-
Mar 25, 2009, 19:24 #8
- 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?
-
Mar 25, 2009, 19:27 #9
- Join Date
- Jul 2006
- Location
- Augusta, Georgia, United States
- Posts
- 4,194
- Mentioned
- 17 Post(s)
- Tagged
- 5 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'));
Code:$post = Post::find( 'one' ,array( 'include'=>'comments' ,'id'=>7 ) ,array( 'require'=>false ) );
Code:echo '<h3>',$post->title,'</h3>'; if($post->comments) { foreach($post->comments as $comment) { echo '<p>',$comment->message,'</p>'; } }
Code:$post = Post::find( 'one' ,array( 'include'=>array('comments','user') ,'id'=>7 ) ,array( 'require'=>false ,'include'=>'user' ) ,array( 'require'=>false ) );
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>'; } }
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'));
-
Mar 25, 2009, 19:49 #10
- Join Date
- Jul 2006
- Location
- Augusta, Georgia, United States
- Posts
- 4,194
- Mentioned
- 17 Post(s)
- Tagged
- 5 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?
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.
-
Mar 25, 2009, 22:11 #11
- 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