I just realized that i may not be following best practices in regards to the MVC pattern.
My issue is that my views "know" information about my database
Here's my situation in psuedo code...
My controller invokes a method from my model and passes it directly to the view
view.records = tableGateway.getRecords() // gets array of records
view.display()
in my view
each records as record
print record.name
print record.address
...
In my view i have record.name and record.address, info that's hard-coded to my database. Is this bad?
What other ways around it are there other than iterating over everything in the controller and basically rewriting the records collection. And that just seems silly.
Thanks
EDIT
Heres an actual view
<?php foreach( $categories as $category ): ?>
<tr>
<td><?php echo $category['name'] ?> </td>
<td><?php echo $category['fields'] ?> </td>
<td><?php echo $category['records'] ?></td>
<td>View</td>
</tr>
<?php endforeach; ?>
So a simple loop through the data won't work, i need to capture certain fields of the sql result in my view.
Is there a way around this? It makes me feel dirty.
I'd say it's not bad to have such info hardcoded if you need to have it quick and dirty.
But consider having generic class for views with method that takes your data from db and some array describing which columns to use. Then in the children classes (UserView, PostView, WhateverTableNameView) you could call this base method with array containing "Name", "Address" etc.
Pardon me if I am talking Python gibberish, I came to this question from PHP tag ;) More or less like this
class BaseView {
public function display(& $data, array $columnNames) {
foreach($data as $row) {
foreach($columnNames as $c) {
echo $row->$c; // or $row[$c] or whatever your data is, I'm assuming objects
}
echo "\n";
}
}
class UserView extends BaseView{
public function display(& $data) {
parent::display($data, array('Name', 'Address');
}
}
The nice things here:
Need one more column? Make sure you query for it, then modify 1 line in UserView.
Need to have text for HTML column labels (<th> stuff) - it's already here.
$data could be resource descriptor (think while($rs.nextRow())) and not neccessarily full array that might occupy a lot of memory and take time to pass around from one function to another.
if you go for nice looking HTML tables around these records, you have unified look & feel across application as there's only one place where you define them.
If for some reason this doesn't appeal to you, the truly generic solution is to have indexes instead of column names. $data[$i][0], $data[$i][1] and so on... Most database APIs offer possibility to query for columns as names, as numbers or both. For PHP + MySQL see examples on http://www.php.net/manual/en/function.mysql-fetch-array.php
But this will bite you in the a$$ sooner or later because you lose metadata info. Let's say you want later to wrap your "names" into links:
echo '',$record['name'],'';
Good luck doing this in reusable way without column names...
With getters/setters and a piece of code to map the record fields to them you can remove this, but you'll add some complexity.
The real question is: Do i need to rename field names at all?
With some planning/thinking/feedback it shouldn't be hard to find appropriate names for your fields that survive the applications lifetime. However, if the semantics of the field change you should add a new field. This has also the advantage that you can clearly document the deprecation of it and lead the programmer to the new one.
Related
I'm new to OOP and MVC and currently building a website using CodeIgniter.
There seems to be a lot of contrasting information about whether loops should be in the view or the model.
On the one hand I'm trying to keep all my html markup inside the views, but on the other hand I want to keep my messy PHP logic outside of the view. Plus I also need to format the data inside my loops using functions located in my model.
What's the best way to go about organising this?
Here is a simplified version of my current implementation:
View
<section>
<ul>
<?php echo $albumTracklistHtml ?>
</ul>
</section>
Controller
$data = [
'$albumTracklistHtml' => $this->MyModel->getAlbumTracklistHtml()
];
$this->load->view('myPage', $data);
Model
public function getAlbumTracklistHtml()
{
//$this->tracklisting returned from db call in other function
foreach($this->tracklisting as $song) {
$mp3 = $this->convertToAmazonUrl($song['mp3']);
$art = $this->formatArtUrl($song['art']);
$name = $this->formatTrackName($song['name']);
$class = 'mp3';
$btn ='';
if(substr($name, 0, 1) == '*') {
$class = 'load mp3';
$btn = '<span class="playBtn"></span>';
}
<li class="'.$class.'" '.$mp3.'>'.$btn.$name.'</li>';
}
}
Very generally speaking, and keep in mind that this isn't a hard and fast rule and if you ask ten different people you'll get ten slightly different answers, but the job of the model view and controller are essentially:
The model provides a way for the controller & view to access data from another source (a database, for instance). It's basically an abstraction on whatever your data is stored in.
The views simply display data they are given.
Controllers connect the model's data with the view, so it can display the data.
I would argue that the example code you've posted is just fine, and fits these definitions. Your model retrieves the data (or processes it), the controller hands the resulting data to the view, and the view simply displays it.
However, I also think it's fine (and generally I prefer this) for the model to simply return a list of items, and then for the view to loop through them and display each one. Of course, the view "shouldn't" be doing a lot of processing, but outputting HTML for each item seems like exactly what it should be doing. The reason I prefer this is purely for separating concerns - your models should be fairly HTML-agnostic. As in, if you ever wrote a non-web-based application to interact with the same data, it could use the same models. Because of this, I would put any HTML-rendering code in my views. Even though it requires some looping logic.
At the end of the day, though, I don't think it matters that much in your case. If you strongly prefer putting the loop in the model, go with that. The most important thing is just to develop your own conventions, and then stick to them.
Here's how I would do the view:
<section>
<ul>
<?php foreach($album->getTracks() as $track): ?>
<li
class="<?php echo $track->isPlayable() ? 'load mp3' : '' ?>"
>
<span class="playBtn">
<?php echo $track->getName() ?>
</span>
</li>
<?php endforeach ?>
</ul>
</section>
This assumes that you've passed a variable called $album, and that a method offered therein returns an array of type Track.
You can return arrays if you like as well, however I prefer objects as you can convert complex conditions to simple, meaningful names. Thus, rather than your '*' test, the programmer calls $track->isPlayable(), which makes much more sense, and doesn't need commenting in the template.
In my experience it depends on what the loop is doing. One can have view specific loops. You can loop through html tags that display elements dynamically and that should go in the view.
for($controller_sent_array as $element)
{
echo "<h4>$element</h4>";
}
However things like looping through file input should go in the controller/model side. I make a point of being vague here because the choice of what should be in models verses controllers depends on framework optimizations. However what is important is that the model and controller are not sending html to be rendered. Rather, they should process data either from a database or user input or network connection, and package that data for the view to figure out. To this end you also need loops.
// This is not a safe way to do this in real life...
for($_POST as $post_input)
{
$this->your_database_library->save_input($post_input);
}
Consider your framework selection with respect to what loops are processing what where, but loops should be used when needed in the model, view, or controller.
To implement this feature I had created a a column replyTo which relates to the comment_id in the same table. The original comments have this as 0. The problem I face is what could be the best way to show them.
In my model, I have created a function nested_comments() to get them and in my view I was doing like, this.
<? foreach( $comments as $comment ): ?>
....
<? foreach( $comment->nested_comments() as $comm): ?>
But that is only fine if I have one level of nested comments. Actually, I wish if there was a way to do this in the view. I don't wish to create another view file and controller for this purpose alone.
You can use function, Like below:
function getComments($comments){
if(!is_array($comments)){
return;
}
foreach($comments as $key => $value){
// do what you want with comments
getComments($nestedComments);
}
}
it's not ready to use function, but you can work in the same way.
You can achieve it with mptt modules, there are a few around:
https://github.com/spadefoot/kohana-orm-leap
https://github.com/rafi/orm-mptt
https://github.com/evopix/orm-mptt
I've been using third one but now its old and I suggest you to take a look at leap, it looks very promising.
And also take a look at http://kohana-modules.com/search?query=mptt
I'm using Doctrine2 and CodeIgniter2, and am new to both, as well as to OOP/MVC, so please use simple explanations :)
For testing purposes, I have a Controller a Model and a View. I want to display data from a table that contains user information. First name, last name, ID number, and so forth.
My controller makes a call to the model- which retrieves data from the doctrine entity, and then the controller passes that data to the view.
(controller)
class Test_v_to_m extends CI_Controller {
public function index() {
$this->load->model('testing/test_v_to_m_model');
$data = $this->test_v_to_m_model->display_user_info();
$this->load->view('testing/test_v_to_m_view', $data );
}
}
(model)
class Test_v_to_m_model extends CI_Model{
public function display_user_name() {
$query = $this->doctrine->em->createQuery("select u from ORM\Dynasties2\Users u");
return $query->getResult();
(view)
//print_r($data);
First question is: How do I pass the object or array along to the view in a useful way? This works if I'm just dealing with a single variable:
(controller)
$user = $this->doctrine->em->find('Entities\User', $user_id);
$data['firstname'] = $user->getFirstName();
$this->load->view('testing/test_v_to_c_view_2',$data);
(view)
echo $firstname;
But I don't know how to do something similar when its an array, or a multidimensional array.
The second question is whether or not to let the view do any real work (php logic, loops, foreach, etc) or to do all of that in the controller and have the view only do formatting and display.
Yes, You can just pass multi-dimensional array to the view and then access it as required.
e.g.
$template_date['result_arr'] = array(
array('firstname' => 'abc', 'lastname' => 'xyz')
, array('firstname' => 'abc', 'lastname' => 'xyz')
);
in your view file -
foreach($result_arr as $key => $row) {
echo $row['firstname'].' <br />';
}
Re your 2nd question - As per my understanding - it's fine to use some foreach, for loops in the view but it's best if business logic is kept to controllers and models. Hope it makes sense to you.
As for your first question, I don't know the answer off the top of my head (sorry!). I would imagine, however, that an array can be passed as part of the data (as a single item), but you would need to iterate though it in the view (see below). Just a guess, however...
As for your second question, the principle of MVC is to have only display logic in the view - so all of the "real work" should be done in the controller.
Now, if you want to have a loop to display data in a table, that's "real work" being done in the view, but since it's part of formatting and display that would be acceptable.
Regarding your first question, it's actually quite simple:
$data = array(
'firstname' => 'string',
'array' => array(1, 2, 3),
'multidimensional_array' => array('ocean' => 'deep')
);
In the view, you can access these as:
$firstname;
$array;
$multidimensional_array;
They're just exported to the view, so you can treat each key in the $data array as a variable, and the values in the $data array as the variables' values.
Regarding the second question, it is generally best if you have the view only do formatting and display. In some cases, it might be useful to use ifs or loops, for example, if you want to display different messages based on a certain variable, or if you want to fill a table with a bunch of rows. However, I strongly recommend that you keep out as much logic as possible. The view is meant to receive all the data it needs and display it in a way that suits it.
There are plenty of reasons for this, namely maintainability (if your logic changes, you don't need to update the view), reusability (if you make views as general as possible, you can reuse them very easily) and even the ability to create new views or to replace that view with a different one, without worrying about the logic.
I hope this helps. :)
I was woundering what is the best coding practice when dealing with the Model-View-Controller setup when using database queries. Should I pass the returned query to the controller, like so ...
Controller
$query = $this->db->get();
$this->template->write_view('content', 'work/index', array('work_query' => $query));
$this->template->render();
View
<?php if ($work_query->num_rows() == 0): ?>
<p>There are no works</p>
<?php else: ?>
<?php foreach($work_query->result() as $work): ?>
//Do something
<?php endforeach; ?>
<?php endif; ?>
Or should I call some function from the query class (for example result_array()) and pass that to the view.
Controller
$query = $this->db->get()->result_array();
$this->template->write_view('content', 'work/index', array('works' => $query));
$this->template->render();
View
<?php if (empty($works)): ?>
<p>There are no works</p>
<?php else: ?>
<?php foreach($works as $work): ?>
//Do something
<?php endforeach; ?>
<?php endif; ?>
Is one considered a better coding standard? Is one more efficient than the other?
As pinadelsai already said, logic tells to put your query inside the Model. That servers 2 purposes: 1) mantain a stricter and better coding practice by separating the three logics bheind MVC; 2) keep your code organized and easier to mantain the day you want to make some changes in your query.
It looks you're calling the query just in a single rendred view, but what if you call it in more than once, and you decide one day to change it? You'll need to check all the views and make changes in each of them; by keeping the logic inside the model, you just change one method and you serve the corrected results to all the views at once.
A small addition and a piece of advice: you needn't necessarily call an array-returning function to have results in array form. Doing something like (in your Model):
$query = $this->db->get('mytable'); // this is your fetching;
$data = array();
foreach($query->result() as $row)
{
$data[] = $row;
}
return $data;
You will have a properties which is always in array form ($data), no matter if you choose to use result() and later change it to result_array(); your view will keep the same foreach logic, you will just need to change how you call the values (using array notation or object notation).
UPDATE:
as per your comment, you can either use a different view for when you have no results (but that strongly depends on your design. More commonly, you let the model pass with its data the information on the amount for results. For example:
$query = $this->db->get('mytable'); // this is your fetching;
$data = array();
if($query->num_rows() > 0)
{
foreach($query->result() as $row)
{
$data[] = $row;
}
}
return $data;
In this case, $data starts as an empty array. If rows are returned, the array is populated, otherwise is returned empty as it is. And then, you decide in your view (or in our controller, if you need to load a whole different view in case) how to differentiate this condition by doing like you already do in your code.
Apart from all of this, there's no enforcing law you need to compel to. You can place whatever logic you want inside your view and CI will work nonetheless. Doing a strict separation of Business logic, Data manipulation and Data display just ensures your app will be much more managable in the future; even if you'll be the only one mantaining your code, I'm pretty sure six month from now, when you go back to those views containing a query, you'll curse yourself from not having done the "right" MVC way.
Better not to pass query in your view. Idle practice and core concept of MVC architecture is to separate Controller(Logic) - Model(Code Behind/ Data Manipulation) - View(Templates).
To pass your complete result set in array to the view is recommended. You can pass your query to view BUT in Rare/Specific cases. [e.g you need some data in footer for this case call your model query in view so that you don't have to render model query in each function of controller and pass it to the view.]
I hope, here i made ma point clear.
Thanks.
I'm not quite grokking a couple of things in OOP and I'm going to use a fictional understanding of SO to see if I can get help understand.
So, on this page we have a question. You can comment on the question. There are also answers. You can comment on the answers.
Question
- comment
- comment
- comment
Answer
-comment
Answer
-comment
-comment
-comment
Answer
-comment
-comment
So, I'm imagining a very high level understanding of this type of system (in PHP, not .Net as I am not yet familiar with .Net) would be like:
$question = new Question;
$question->load($this_question_id); // from the URL probably
echo $question->getTitle();
To load the answers, I imagine it's something like this ("A"):
$answers = new Answers;
$answers->loadFromQuestion($question->getID()); // or $answers->loadFromQuestion($this_question_id);
while($answer = $answers->getAnswer())
{
echo $answer->showFormatted();
}
Or, would you do ("B"):
$answers->setQuestion($question); // inject the whole obj, so we have access to all the data and public methods in $question
$answers->loadFromQuestion(); // the ID would be found via $this->question->getID() instead of from the argument passed in
while($answer = $answers->getAnswer())
{
echo $answer->showFormatted();
}
I guess my problem is, I don't know when or if I should be passing in an entire object, and when I should just be passing in a value. Passing in the entire object gives me a lot of flexibility, but it's more memory and subject to change, I'd guess (like a property or method rename). If "A" style is better, why not just use a function? OOP seems pointless here.
Thanks,
Hans
While I like Jason's answer, it is not, strictly speaking OO.
$question = new Question($id);
$comments = $question->getComments();
$answers = $question->getAnswers();
echo $question->getTitle();
echo $question->getText();
foreach ($comments as $comment)
echo $comments->getText();
The problems are:
There is no information hiding, a fundamental principle of OO.
If the format of the answers needs to change, it must be changed in a place that is not associated with the object that houses the data.
The solution is not extensible. (There is no behaviour to inherit.)
You must keep behaviour (tightly coupled) with the data. Otherwise you are not writing OO.
$question = new Question($id);
$questionView = new QuestionView( $question );
$questionView->displayComments();
$questionView->displayAnswers();
How the information is displayed is now an implementation detail, and reusable.
Notice how this opens up the following possibility:
$question = new Question( $id );
$questionView = new QuestionView( $question );
$questionView->setPrinterFriendly();
$questionView->displayComments();
$questionView->displayAnswers();
The idea is that now you can change how the questions are formatted from a single location in the code base. You can support multiple formats for the comments and answers without the calling code (a) ever knowing; and (b) ever needing to change (to a significant degree).
If you are coding text formatting details in more than one location because you are misusing accessor methods, the life of any future maintainers will be miserable. If the maintainer is a psychopath who knows where you live, you will be in trouble.
Objects, Data, and Views
Here's the problem, as I understand it:
Database -> Object -> Display Content
You want to keep the behaviour of the object centred around logic that is intrinsic to the object. In other words, you don't want the Object to have to do things that have nothing to do with its core responsibilities. Most commonly this will include load, save, and print functionality. You want to keep these separate from the object itself because if you ever have to change database, or output format, you want to make as few changes in the system as possible, and restrain the ripple effect.
To simplify this, let's take a look at loading only Comments; everything is applicable to Questions and Answers as well.
Comment Class
The Comment class might offer the following behaviours:
Reply
Delete
Update (requires permission)
Restore (from a delete)
etc.
CommentDB Class
We can create a CommentDB object that knows how to manipulate the Comments in the database. A CommentDB object has the following behaviours:
Create
Load
Save
Update
Delete
Restore
Notice that these behaviours will likely be common across all objects and can therefore be subject to refactoring. This will also let you change databases quite easily as the connection information will be isolated to a single class (the grandfather of all database objects).
Example usage:
$commentDb = new CommentDB();
$comment = $commentDb->create();
Later:
$comment->update( "new text" );
Notice that there are a number of possible ways to implement this, but you can always do so without violating encapsulation and information hiding.
CommentView Class
Lastly, the CommentView class will be tightly coupled to a Comment class. That it can obtain the attributes of Comment class via accessors is expected. The information is still hidden from the rest of the system. The Comment and its CommentView are tightly coupled. The idea is that the formatting is kept in a single place, not scattered throughout classes that need to use the data willy nilly.
Any classes that need to display comments, but in a slightly different format, can inherit from CommentView.
See also: Allen Holub wrote "You should never use get/set functions", is he correct?
Why pass either? What about:
<?php
$question = new Question($id);
$comments = $question->getComments();
$answers = $question->getAnswers();
echo $question->getTitle();
echo $question->getText();
foreach ($comments as $comment)
echo $comments->getText();
foreach ($answers as $answer)
{
$answer_comments = $answer->getComments();
echo $answer->getText();
foreach ($answer_comments as $comment)
echo $comment->getText();
}
Where getComments() and getAnswers() use $this->id to retrieve and return an array of comment or answer objects?
You could build utility methods in the comment and answer objects that allow you to load by parent id. In which case, just taking an id as a parameter would be nice.
$question = new Question($id);
$answers = Answer::forQuestion($question->id);
$comments = Comment::forQuestion($question->id);
$ans_comments = Comment::forAnswer($answer->id); // or some way to distinguish what the parent object is.
Edit: Likely the child model (Comment or Answer in this case) doesn't need anything from the parent except and id to do db queries with. Passing in the entire parent object would be overkill. (Also, PHP has a terrible time garbage collecting objects with circular references, which might be fixed in the 5.3 series.)
Both styles are acceptable. Sometimes you only need the value, sometimes you'll need the object. In this example I would personally do something along the lines of your first example, but trivial programs like this don't tend to exist in the wild very often so maybe you want the second piece.
My rule of thumb is to do the thing in the least number of lines that still clearly demonstrates what you're attempting to do to anyone who comes after you. The overhead of most object creation vs value passing is something you'll likely never ever have to deal with on modern arch.
Adding to what #jasonbar already mentioned:
I don't know when or if I should be passing in an entire object, and when I should just be passing in a value.
It depends on the Coupling you need and the Cohesion you desire.
Passing in the entire object gives me a lot of flexibility, but it's more memory and subject to change.
PHP does not copy the object when you use it as an argument to a function. Neither do most other languages (either by default, like C# and Java, or upon explicit request, like C and C++)
To add to Dave Jarvis and jasonbar answers, I usually have DataMappers to convert between relational data and objects, instead of using an ActiveRecord approach. So, following your example, we would have these classes:
Question
Answer
Comment
and their data mappers:
QuestionMapper
AnswerMapper
CommentMapper
Each mapper implementing a similar interface:
save(object) // creates or updates a record in the database (or text file, for that matter)
delete(id)
get(id)
Then, we would do as:
$q = QuestionMapper::get( $questionid );
// here we could either (a) just return a list of Answers
// previously eagerly-loaded by the
// QuestionMapper, or (b) lazy load the answers by
// calling AnswerMapper::getByQuestionID( $this->id ) or similar.
$aAnswers = $q->getAnswers();
foreach($aAnswers as $oAnswer){
echo $oAnswer->getText();
$aComments = $oAnswer->getComments();
foreach($aComments as $oComment){
echo $oComment->getText();
}
}
Regarding the use of things like QuestionView->render( $question ), I prefer to have Views which display the data using getters from the domain objects. If you pass a Question to a HTMLView, it will render it as HTML; if you pass it to a JSONView, then you'll get JSON-formatted content. This means that the domain objects need to have getters.
PS: We could also consider the QuestionMapper to load everything related to Questions, Answers, and Comments. Since Comments always belongs to Answers or Questions, and Answers always belong to Questions, it could make sense that the QuestionMapper loaded everything. Of course we would have to consider different strategies for lazy loading a Question's set of Answers and Comments, to avoid hogging the server.