I'm working on a PHP based Model-View-Controller structured website. I understand that the Models should deal with business logic, views present HTML (or whatever) to the user, and the controllers facilitate this. Where I'm running stuck is with forms. How much processing do I put in the controller, and how much do I put the my model?
Assume that I'm trying to update a user's first & last name. What I want to do is submit a form using AJAX to one of my controllers. I want the data to be validated (again) server side, and if valid save it to the database, and then return a JSON response back to the view, as either a success or error.
Should I create an instance of my user model in the controller, or should I just have the controller relay to a static method in my model? Here is two examples of how this could work:
Option #1: Process POST in Model
<form action="/user/edit-user-form-submit/" method="post">
<input type="text" name="firstname">
<input type="text" name="lastname">
<button type="submit">Save</button>
</form>
<?php
class user
{
public function __construct($id){} // load user from database
public function set_firstname(){} // validate and set first name
public function set_lastname(){} // validate and set last name
public function save_to_database(){} // save object fields to database
public static function save_data_from_post()
{
// Load the user
$user = new user($_POST['id']);
// Was the record found in the db?
if($user->exists)
{
// Try to set these fields
if(
$user->set_firstname($_POST['firstname'])
and
$user->set_lastname($_POST['lastname'])
)
{
// No errors, save to the dabase
$user->save_to_database();
// Return success to view
echo json_encode(array('success' => true));
}
else
{
// Error, data not valid!
echo json_encode(array('success' => false));
}
}
else
{
// Error, user not found!
echo json_encode(array('success' => false));
}
}
}
class user_controller extends controller
{
public function edit_user_form()
{
$view = new view('edit_user_form.php');
}
public function edit_user_form_submit()
{
user::save_data_from_post();
}
}
?>
Option #1: Process POST in Model
<form action="/user/edit-user-form-submit/" method="post">
<input type="text" name="firstname">
<input type="text" name="lastname">
<button type="submit">Save</button>
</form>
<?php
class user
{
public function __construct($id){} // load user from database
public function set_firstname(){} // validate and set first name
public function set_lastname(){} // validate and set last name
public function save_to_database(){} // save object fields to database
}
class user_controller extends controller
{
public function edit_user_form()
{
$view = new view('edit_user_form.php');
}
public function edit_user_form_submit()
{
// Load the user
$user = new user($_POST['id']);
// Was the record found in the db?
if($user->exists)
{
// Try to set these fields
if(
$user->set_firstname($_POST['firstname'])
and
$user->set_lastname($_POST['lastname'])
)
{
// No errors, save to the dabase
$user->save_to_database();
// Return success to view
echo json_encode(array('success' => true));
}
else
{
// Error, data not valid!
echo json_encode(array('success' => false));
}
}
else
{
// Error, user not found!
echo json_encode(array('success' => false));
}
}
}
?>
The two examples do the exact same thing, I realize that. But is there a right and wrong way of doing this? I've read a lot about skinny controllers and fat models, where is where option #1 came from. How are you handling this? Thanks, and sorry for the long question!
Put shortly, you can use either of these approaches - but you should change them a bit.
Consider this: The models don't really "know" about post, get and whatnot. They should only know about whatever business-related thing they are - in your case a user.
So while approach #1 can be used, you should not access post variables directly from the model. Instead, make the function take an array of parameters which are then used to create the user.
This way you can easily reuse the code, say in a shell script or whatever, where there is no such thing as
$_POST
.While the second approach is more verbose in the controller, it's something you could do too. However, perhaps a bit better approach in the style is to use a "service class". The service would have a method, let's say "createUserFromArray", which takes an array and returns a user. Again, you would pass this method the
$_POST
as parameters - similar to how you should pass them into the function in modified #1.Only the controller should deal with inputs directly. This is because the controller handles the request, and thus it can know about post.
tl;dr your models should never use superglobals like
$_POST
directly.From my point of view and the way we're doing it at work, model will handle the validation and filtering of datas that are passed to it, but we use the controller to push thoose datas inside the model.
Has stated in above comments, the model don't have to know about $_POST or $_GET these are user input that the controller has to deal with. In the other hand the model must handle all verification of datas passed to it as you definitely don't want to make again and again your data validation in different code portion of your project.
A big reason for the MVC design pattern is that it's a good way of maintaining Separation of Concerns. The View should be ignorant of the Model, and vice-versa. The Controller is there only as a sort of traffic cop mediating between the Model and the View. So the Controller should take the data from the View, do the minimal processing needed so that the Model can understand the data without needing to know how the View is implemented (i.e. via an HTML form), and give it to the Model so the model can persist the data.
This makes it so the Model can be reused in other instances when an item needs to be created / saved / persisted by other means than an HTML form, without duplicating item-saving code across multiple Controllers.
UPDATE: I forgot to mention validation. Along the same lines as for persisting the data, the Controller should take the data and pass it to the Model for validation, since the Model is the one that knows the exact format of the data it needs. You could combine validation and persistence by having the Model throw an exception if the data is invalid, which the Controller can catch and deal with as necessary (e.g. render JSON error response.)