i am starting to put validation/sanitization in my codeigniter models, and before diving in too deep I am looking for some suggestions on best practices. the form validation library for controllers is great, but obviously i don't want to rely on the controllers to send me good data.
currently I return bool values, TRUE (or data) on success, FALSE on failure, which makes it really hard to pass error messages back to the caller. I would like to get away from the FALSE on failure.
while definitely not an expert, i have started reading quite a bit on Exceptions and have come across them quite a bit with external libraries, and they seem like a good candidate for this. my question is, is this appropriate use of exceptions? are model errors exceptional errors?
a possible example:
<?php
class person_model extends CI_Model{
public function getPersonById($personId){
//check for int
if(!is_int($personId) OR $personId < 0){
throw new Exception('Invalid person ID');
}
//setup query
$this->db->select('*')
->where('personId', $personId);
//run query
$result = $this->db->get('person');
//failed to get
if(!$result){
throw new Exception('DB query failed');
//should i also return false?
return FALSE;
}
//got info
else{
return $result;
}
}
}
?>
thanks for the help!
EDIT:
I have to say I am quite surprised by the responses suggesting that data validation should only be done in the controller. Models are the last barrier to your data storage. The model is the data and the rules applying to that data, your application logic. Data validation seems like application logic to me. Also you may have many controllers accessing the same model method. Do you want to rely on both controllers implementing the same validation? That seems silly to me.
Also, not all data is coming from user input, some of it could be hardcoded into the script by your programmer writing the controller. What if they pass a string when your model is expecting an integer? Or pass an incorrectly formatted date? shouldn't the model say something about that.
I'm open to a discussion, but I feel data validation DEFINITELY belongs in the model. (in addition to the controller, and even the view (html5/javascript for convenience))
the easiest way i've found to deal with this is to always check for the negative condition first with an if check. this also makes it easy to check multiple steps.
always return something back from your model methods whenever possible. Like even in this example - we need to validate the form - i would use codeigniters form validation to validate that its an integer etc. if it passes validation then we need the $personId for the database search. So instead of just returning true/false from the validation - if validation passes then return the $personId :
function getperson() {
// Validate the form
// AND if its valid, return the validated $personId
// Note the separate private method if the validation failed
if ( ! $personId = $this->person->_validateGetPersonForm() ) {
$this->error_msg = 'Error in validating the form. Please use a number.';
$this->_showGetPersonFailed() ; }
elseif ( ! $person = $this->person->getBy($personId) ) {
$this->error_msg = 'There is no person in our records with the number:'. $personId;
$this->_showGetPersonFailed() ; }
else { $this->_showResultsFor($person) ; }
}
$this->error_msg is automatically available for any of your views and because we have broken out validation and the database search - the error message is easily appropriate for the exact condition. if the search failed then there is a private method _showGetPersonFailed() to show the form again. the last if / else is usually success - there is a separate private method to handle showing the results with the appropriate views.
also suggest not using the word "model" in the file name like "person_model". it just clutters up the overall naming and forces you to type the word model over and over again :-) think of it this way: if you are calling something from a controller and getting back results - its almost always going to be a model. and all your model files will always be in a folder named models.
Go to:
application/config/database.php
and search for db_debug to be TRUE. For example:
...
$db['default']['dbprefix'] = '';
$db['default']['pconnect'] = TRUE;
$db['default']['db_debug'] = TRUE; //<-- Have this to true
$db['default']['cache_on'] = FALSE;
$db['default']['cachedir'] = '';
...
In my models, I never throw exceptions or errors. I always return false on a query that returns null. If there is an SQL error in your code you will be notified by codeigniter automatically. I would stay away from throwing exceptions if your query returns no results.
You can then handle the query if it returns false with your controller.
EDIT: Also you should check the data being queried into the database in the controller, and not the model. In my opinion the model should be used strictly for querying data, not for error/data checking, you can do this before hand in the controller when it is submitted.
An example:
Model
function search_replies($term){
$this->db->like('ticket_id', $term);
$this->db->or_like('reply_from', $term);
$this->db->or_like('reply_from_name', $term);
$this->db->or_like('reply_content', $term);
$query = $this->db->get($this->table_ticket_replies);
// Returns the result if the number of rows is greater than 0, returns false otherwise
if ($query->num_rows() > 0) return $query->result();
return false;
}
Controller
function example_controller(){
if($this->search_model->search_replies('Test')){
$data['results'] = $this->search_model->search_replies('Test');
}
$this->load->view('search_results', $data);
}
View
<?php
if(isset($results)){
echo 'Retrieved Results';
foreach($results as $result){
}
} else{
?>
<h2>No results for search term!</h2>
<?php
}
?>
You have probably missed out this part of the form validation page in the user guide:
Showing Errors Individually
Usage:
echo form_error('field_name');
Just put in whatever field name you are using in place of "field_name", for example "username" or "email" etc.
Related
I have Php Login system using MVC structure. For database data validation I create LoginModel. I need to print failure error to view like: User Not Exist Now Which way is right and better:
1- Add error data validation in Login Model and get in Controller and Print to View Like This:
class LoginModel extends \App\Core\Model
{
public function login($user_name, $user_password, $set_remember_me_cookie = null)
{
$returnError = array();
// checks if user exists, if login is not blocked (due to failed logins) and if password fits the hash
$result = $this->validateAndGetUser($user_name, $user_password);
// check if that user exists.
if (!$result) {
$returnError['isMessage'] = false;
$returnError['name'] = "User Not Found";
}
return $returnError;
}
private function validateAndGetUser($user_name, $user_password){
//Check User Data Validation
}
}
2- Add Only True Or False in LoginModel and Get in Controller And Set Error Name and Print to View Like This:
class LoginModel extends \App\Core\Model
{
public function login($user_name, $user_password, $set_remember_me_cookie = null)
{
// checks if user exists, if login is not blocked (due to failed logins) and if password fits the hash
$result = $this->validateAndGetUser($user_name, $user_password);
// check if that user exists.
if (!$result) {
return false;
}
return true;
}
private function validateAndGetUser($user_name, $user_password){
//Check User Data Validation
}
}
In action my really question is: Can I add error message in Model and Get in Controller?! Which way is right and true?
There are many ways to do this.
For me the best way is the first way you have suggested, but better declare bug reports in a single file eg errors.php and make the model return you array with 2 keys, the first key is always TRUE or FALSE and then if your model returns the first key FALSE reads the error number from the second key.
Then, in the controller you can replace the error number with the corresponding key in the array you declared in errors.php
If the first key is TRUE, then the second key will be your user information.
Suggestion:
Split the login functionality into two (main) steps:
Check if the posted user already exists. If not, throw an exception.
Match the posted password against the stored one. If they don't match, throw an exception. I suggest the use of password_hash for storing passwords, and of password_verify for matching a password with the stored one.
Then - based on your current architecture, in your controller, use a try-catch block to catch the exception thrown by the login steps and proceed as you wish to display the exception message to the user.
Just as a note: In case of an ajax request, you would send a specific response header (with status code 500, for example) or a custom response header (with status code 420, for example) back to the client, e.g. browser, in order to activate the error function of the ajax request.
Notes:
The domain model is a layer. And it must have no knowledge about the outside world.
A controller should only (!) update the domain model (through services).
A view should be a class - not a template file - responsible with fetching data from the model layer (through services), preparing/formatting it for presentation, and passing it to a response object, in order for this to be returned and printed.
The controller and the view (mostly 1:1 relation) should be created separately. The controller should not have any knowledge about the view. This creation step would take place in the front-controller class or file.
As for error reporting, I would recommend to read this.
I am sorry for asking this type of question but I'm getting really frustrated by this situation
This is my controller
public function changeName_PUT() {
header('Content-Type: application/json');
$return = \Models\User::getCurrent()->changeName(Input::data());
return json_encode($return);
}
And this is my model
public function changeName($data){
$rules = array( ... );
$messages = array( ... );
$valid = Input::validate($data, $rules, $messages);
if($valid === true){
return $this->putIntoDb(array('name' => $data['name'])) ? 'Namechanged successfully.' : 'An error occurred.';
}else{
return $valid->getMessage();
}
}
My problem is that no matter whether the model succeeds or fails there is always a string returned, so in the controller I cannot determine if the name was changed or not. If I return only true or false then in the controller, if something failed, I wouldn't know what it was. My other option is to return arrays from the model like array('error', 'message') but that looks so ugly and gives me the feeling that I'm doing it wrong. Can you point me the right way?
You have different approaches :
Simple version : If you insert, return the inserted_id|false. If you delete, return true|false. If you update, return true|false. If you select, return array|string|false.
Class version : You return true or false (except for select which returns the queried string|array). And you store in a class variable the error messages so that you can do something like :
$success = $model->query('blabla');
if (!$success)
print_r($model->getMessages());
Object version : Same as the simple version, except that you return the database object on a successful select or update query (else false).
Exception version : Same as object version, but instead of returning false, you throw an error that describes the problem.
Now, if you look at common frameworks you will see that CodeIgniter uses the object version, Phalcon uses the class version, Zend uses the exception version.
There is no "best-way", just use a method and stick with it for all your models.
(Just do not return a string that explains the error on a failed query!)
Model layer should not be sending data to the controllers at all.
In the MVC architectural pattern the controllers are responsible for altering the state of model layer. And that's it. View instances then pull what information they need from said model layer.
If, because of controller's alterations, an error state is triggered in the model layer, then controller does not care about it. It is only important to the view, which, based on whether an error happened, might pick a different set of templates for assembly of response.
P.S.: the HTTP headers are part of response. So, why are your "controllers" generating a response? It's basically no different from putting an echo there.
Since today I have started validating form data in my Model layer instead of in my Controllers. I am going to shorten the code snippets as much as possible.
This is a method from my User domain object (setLastName() method is basically the same)
public function setFirstName($firstName) {
if(!$firstName) throw new \InvalidArgumentException('Some message');
if( strlen($firstName) < 2 || strlen($firstName) > 20 ) throw new \LengthException('Some message');
if(preg_match('/[^a-zA-Z\'.-\s]/', $firstName)) throw new FormatException('Some message');
$this->firstName = $firstName;
}
In my Controller I have something like this
$userService = $this->serviceFactory->build('User');
try {
$userService->register('John', 'M');
}
catch(\InvalidArgumentException $ex) {
}
catch(\LengthException $ex) {
}
catch(etc etc)
In my UserService method register() I have something like
$user->setFirstName($firstName);
$user->setLastName($lastName);
When running the setFirstName() method it will successfully set the supplied first name. The setLastName() method will throw a LengthException as it is too short.
That is what I want but when that comes back to the service layer and then to the controller and I catch it I know that a LengthException was thrown but I can't give the user a proper message like "The supplied last name was too short" because I do not know for which field the exception was thrown, just the type of exception.
How do I get around this? Thanks.
View instances should be requesting information from the model layer. Controllers is not responsible for passing the information.
This would also mean that you obsessive-compulsive use of exceptions, that cause your abstraction layers to leak, would be completely pointless. "Error" is just a state of model layer. It is an expected situation, not an exception.
Controllers in MVC are responsible for changing the state of model layer and (quite rarely) the state of current view instance. They should not be receiving any feedback from services.
Instead of returning some message, why not return a useful error message such as "supplied First name is too short". This can then be returned to the user.
Alternatively you can see that when extending exceptions you can specify additional information such as numeric codes - you could of course use this.
Or of course you can create a subclass of Exception for difference circumstances, but you could end up with hundreds of Exception subclasses which would of course be messy.
I have the same question. I think that most of the people who say that all the validation should be done in the model never developed a full PHP MVC application themselves and only know books and theory. Never a piece of code is seen about that topic.
Anyway I have thought of a possible solution. What do you think about the code below:
// Controller
$user = User::make(
$_POST['lastname'], $_POST['firstname'],
$_POST['gender'], [...]
);
if(is_array($user)) {
// store the errors in a view variable and forward, or store in session and redirect
$_SESSION['errors'] = $user;
$this->_redirect('add');
exit;
}
// Model
public static make($lastname, $firstname, $gender, [...]) {
$errors = array();
if(/* test firstname */) $errors[] = 'model_error_firstname';
if(/* test lastname */) $errors[] = 'model_error_lastname';
if(!empty($errors)) return $errors;
return new User($lastname, $firstname, $gender, [...]);
}
The model would have a static function that will return either an array with errors in case something went wrong, or a new model object if validation was ok.
In your controller you test whether an array was returned or not.
Maybe I would put the constructor of the User as private, because if you build a user with the constructor directly, you would skip all the validations. But this does not mean that it becomes a Singleton.
Maybe I would also sanitize the form fields and make them safe before passing them to the model.
The keys like model_error_xyz would be found in a translation file with the appropriate text.
Update:
Actually I think that you could just throw a custom exception from the constructor and that contains an array of messages. But the reason that I didn't propose that, is that it leads to half-constructed objects, at least in Java for example, but hey, PHP is not Java...
You would also have to validate each setter function :( it seems tedious to do validation in the model instance.
Any thoughts are welcome.
I've fairly new to PHP MVC and I'm not 100% sold I'm doing this in the most logical way possible. This is a piece of code from one of my controllers. A friend said a majority of this code should be moved into the Model? Is this so?
Right now I have a DataAccess layer that does all the SQL work. For User work I have a UserDataAccess object, which will create, update and delete. It appears I'm using something along the lines of the Data Mapper pattern. My Model classes map directly to the database columns on a given table and that's it.
I'm continually confused at where each piece of code should go.
// Posted values passed all checks.
if(count($errors) == 0)
{
try
{
// Get a Connection.
$connection = ConnectionFactory::GetConnection('mysql');
try
{
$connection->beginTransaction();
// Create DataAccess object.
$uda = new UserDataAccess();
// Check if username and email are avail.
if(count($uda->CheckUsername($connection, $user->Username)) > 0)
$errors['username_taken'] = "Username is taken";
if(count($uda->CheckEmail($connection, $user->Email)) > 0)
$errors['email_taken'] = "Email is taken";
if(count($errors) == 0)
{
// Perform Create.
$userId = $uda->CreateUser($connection, $user->GetArray());
// Add User to Rookie Role.
$uda->AddUserToRole($connection, 1, $userId);
// Commit connection.
$connection->commit();
}
// Clear connection.
$connection = null;
// Redirect user.
if(count($errors) == 0)
header('location: /' . $user->Username);
}
catch(PDOException $e)
{
$connection->rollBack();
$errors['internal'] = "Opps: There was an error processing the request.";
}
}
catch(Exception $e)
{
$errors['internal'] = "Opps: There was an error processing the request.";
}
}
}
Try to move the mysql connection entirely to the model, the controllers should not know what type of database you are using and how you are connecting to it. Try to think of it in this way:
Views only show pages to the users;
Controllers answer to requests by performing calculations, interacting with the Models and loading Views;
You should always be able to change any of the components without affecting the ones that interact with it;
From the looks of it, it seems like some code that sound go into the controller.
Here's a break down of MVC:
Model: Accessing database, preferably using an object oriented method that treats data like object.
View: The HTML of the page.
Controller: Logic that allows a dynamic page to be built.
That probably sounded really abstract. The general idea of views is that they are the HTML template, with minimum codes, preferably only echoing certain dynamic element of the page (NOT THE HTML, just plain text, usually) and/or a bit of if and foreach loops. Example:
.... Your HTML code
<?php foreach ($page->users as $user): /* Loops through all the users */ ?>
<li><?php echo $user->name; /* echo their name */ ?></li> /
<?php endforeach; ?>
.... Your HTML Code
The idea behind controllers is how you get your page to actually work, by handling the logic, getting input, and such. Example:
class Controller extends BaseController{
function indexpage($args=array()){
if ($args[0] == 'user') $user = UserModel::listUsers(); // If the argument to the controller is 'user' (could be provided by GET, or just the URL, we won't go into that), list all users.
$this->render('yourViewPage', array('user' => $user)); // renders the view file named yourViewPage.php and pass the value of $user into a variable named 'user'. As shown by above accessed via $page->user.
}
}
Granted that the above is only a simple example, you get the point. The render() renders the page and passes the key => value pair in the array so that the view has access to them.
The Model is the database interaction. It allows you to access the database without using SQL (preferably). Example:
class UserModel{
public $name;
public $openid;
public static function listUsers($offset=0, $max=20){
global $persister;
return $persister->list('UserModel', 0, 20, array('order'=>'NAME DESC'));
}
}
// Create a new user. This usually goes into the controller
$user = User();
$user->name = 'Your User'; // sets name
$user->openid = 'htto://theiropenidprovider.com/openid'; // sets openid
$persister->track($user); // Adds the user to be tracked. (the tracking info is usually written in XML, but we won't go into that).
$persister->flushAll(); // Saves to the database (kinda like commit)
// Gets the user.
$persister->find('UserModel', 'name', 'Your User') // Find the user that has the name of "Your User" in all UserModel instanced tracked.
So Models don't have to have the most coding. In my opinions controllers would have a lot of code, but that totally depends on the complexity of what you're building.
Hope that clears it up for you.
As you said, the Model is where you should put your dabatase code above. A controller handles what the user sees. In PHP the controller is often not a class but to keep things practical, just the php file which the user navigates to, which controls what happens for that view. The controller should not need to know how the DB is implemented, or even that a DB is used, so for example having transaction information is not good.
I have a table with a unique key (date + userid) in my web application database. When I try to insert record with existing date and userid I receive the following error:
dupicate key in table
I turned on the database debugger in application config, because I needed to use MySQL error numbers and messages. Now I need to process this error. I can use hard coded constants in the controller, but I think it's not good idea. I need database error handling in many places, and I dont like CodeIgniter's error handling. What is a best practice concerning the processing of database errors.
We use in our project constructions like these:
$this->db->_error_number();
$this->db->_error_message();
but this undocumented functions and in next releases this could change.
Of course you can simply use standard php for mysql error handle function:
mysql_errno()
mysql_error()
internally CI use these functions.
As for me the best practice is use in custom Model's base class (BaseModel)
$this->db->_error_number();
and determine error, next throw exception with information error message taken from
$this->db->_error_message();
All your Model derived from BaseModel, and call method to check last request for db error and your Model must handle exception, and process it (may be additionally log), of course you can implement check result as result value and avoid of throwing exception.
I have a different suggestion for this i will recommend this
$this->form_validation->set_rules('email', 'Email', 'required|max_length[32]|valid_email|callback_email_available');
while submitting the form you need to define rules. always use callbacks to interact with database
Controller callback method
public function email_available($str)
{
// You can access $_POST variable
$this->load->model('mymodel');
$result = $this->mymodel->emailAvailability($_POST['email']);
if ($result)
{
$this->form_validation->set_message('email_available', 'The %s already exists');
return FALSE;
}else{
return TRUE;
}
}
And model method
public function emailAvailability($email)
{
$this->db->where('email',$email);
$query = $this->db->get('tablename');
return $query->row();
}
this way you will always avoid database errors on the front and can get user see things in a better way. you dont have to handle db errors because form validation is handling everything for you.