Validate associated models in CakePHP2 - cakephp-2.0

I'm a noob in CakePHP and I've been trying to do some complex validations here:
I have the following models:
- Fonts (name, file);
- Settings(value1,value2,value3,type_id,script_id);
- Types(name)
Whenever I create a Font I also create a default setting associated to it. Also, this setting has a type associated. After the Font is created I can associate more settings to it (Font hasMany Settings), but I need to make sure that two settings of the same type are not added to that font. I don't know how to handle this case. Any help is appreciated. Thanks.

I'd use a simple beforeSave validation
//in setting.php model
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias]['font_id']) && isset($this->data[$this->alias]['type_id']) {
$otherSettings = $this->find('all', array('conditions'=>
array('type_id'=>$this->data[$this->alias]['type_id'],
'font_id'=>$this->data[$this->alias]['font_id']);
//check if it's insert or update
$updated_id = null;
if ($this->id)
$updated_id = $this->id;
if (isset($this->data[$this->alias][$this->primaryKey]))
$updated_id = $this->data[$this->alias][$this->primaryKey];
if (count($otherSettings) > 0) {
if ($updated_id == null)
return false; //it's not an update and we found other records, so fail
foreach ($otherSettings as $similarSetting)
if ($updated_id != $similarSetting['Setting']['id'])
return false; //found a similar record with other id, fail
}
}
return true; //don't forget this, it won't save otherwise
}
That will prevent inserting new settings to the same font with the same type. Have in mind that this validation will return false if the validation is incorrect, but you have to handle how you want to alert the user of the error. You can throw exceptions from the beforeSave and catch them in the controller to display a flash message to the user. Or you could just not save those settings and let the user figure it out (bad practice).
You could also create a similar function in the model like checkPreviousSettings with a similar logic as the one I wrote above, to check if the settings about to be saved are valid, if not display a message to the user before attempting a save.
The option I prefer is the exception handling error, in that case you'd have to replace the return false with
throw new Exception('Setting of the same type already associated to the font');
and catch it in the controller.
Actually, the better approach is to not even display the settings with the same type and font to the user, so he doesn't even have the option of choosing. But this behind-the-scenes validation would also be needed.

Related

Difference in accessing variables in views

I've two controllers one is "Upload" which deals with images uploads and other is "Page" whid deals with the creation of pages of CMS now if in my "Upload" controller I load both the models i.e 'image_m' which deals with image upload and "page_m" which deals with the pages creation I've highlighted the relevant code my problem is if I access the variables in the view
$this->data['images'] = $this->image_m->get(); sent by this I can access in foreach loop as "$images->image_title, $images->image_path" etc
But the variable sent by this line ***$this->data['get_with_images'] = $this->page_m->get_no_parents();*** as $get_with_images->page_name, $get_with_images->page_id etc produces given error
A PHP Error was encountered
Severity: Notice
Message: Trying to get property of non-object
Filename: upload/index.php
Line Number: 20
what is the difference between these two access levels one for $image & other for $get_with_images because I can only access its values as $get_with_images
class Upload extends Admin_Controller {
public function __construct() {
parent::__construct();
***$this->load->model('image_m');
$this->load->model('page_m');***
}
public function index($id = NULL) {
//var_dump($this->data['images'] = $this->image_m->get_with_images());
//$this->data['images'] = $this->image_m->get_with_images();
***$this->data['images'] = $this->image_m->get();***
$this->data['subview'] = 'admin/upload/index';
if ($id) {
$this->data['image'] = $this->image_m->get($id);
count($this->data['image']) || $this->data['errors'][] = 'Page Could not be found';
}
$id == NULL || $this->data['image'] = $this->image_m->get($id);
/*this calls the page_m model function to load all the pages from pages table*/
***$this->data['get_with_images'] = $this->page_m->get_no_parents();***
You are not posting all your code so its hard to tell but is it because you used $this-> in the controller, but you haven't done the same thing in the view?
In this case i would recommend not using $this-> because its not necessary. Also its much better to check for errors etc when you call the model so do something like
if ( ! $data['images'] = $this->image_m->get($id) ) {
// Failure -- show an appropriate view for not getting any images
// am showing $data in case you have other values that are getting passed
$this->load->view( 'sadview', $data ); }
else {
// Success -- show a view to display images
$this->load->view( 'awesomeview', $data ); }
so we are saying if nothing came back - the ! is a negative - then show the failure view. Else $data['images'] came back, and it will be passed to the view. note i have not had to use $this-> for anything and it won't be needed in the view.
Would also suggest using separate methods - have one method to show all images and a separate method like returnimage($id) to show an image based on a specific validated $id.
====== Edit
You can access as many models as you want and pass that data to the View. You have a different issue - the problem is that you are waiting until the View to find out - and then it makes it more difficult to figure out what is wrong.
Look at this page and make sure you understand the differences between query results
http://ellislab.com/codeigniter/user-guide/database/results.html
When you have problems like this the first thing to do is make a simple view, and echo out directly from the model method that is giving you problems. Its probably something very simple but you are having to look through so much code that its difficult to discover.
The next thing is that for every method you write, you need to ask yourself 'what if it doesn't return anything?' and then deal with those conditions as part of your code. Always validate any input coming in to your methods (even links) and always have fallbacks for any method connecting to a database.
On your view do a var_dump($get_with_images) The error being given is that you are trying to use/access $get_with_images as an object but it is not an object.
or better yet on your controller do a
echo '<pre>';
var_dump($this->page_m->get_no_parents());
exit();
maybe your model is not returning anything or is returning something but the data is not an object , maybe an array of object that you still need to loop through in some cases.

How to validate a pair of values from the same Model?

Use case
My use case is that I need to validate a Table Tennis score.
Form
<input name="data[MatchesPlayer][1][score]" type="number" id="MatchesPlayer1Score">
<input name="data[MatchesPlayer][2][score]" type="number" id="MatchesPlayer2Score">
Constraints
One score must be bigger than 11.
One score must be 2 points or greater than the other if the score is higher than 11.
Problem
When cake validates multiple rows from the same model, the model data is set to that record. This means that it's not possible to compare the two values as they aren't both available in $this->data. As I am using saveAll() each record is set to the model and then validated before it's saved.
Question
I'd like to know if there is a good way to validate this pair of data without resorting to saving it into the session or similar before I can validate it.
What I normally do here is I create a wrapper for the save method. This allows me to perform custom manipulation that would otherwise not be possible with model callbacks, or even use custom transactions etc.
In your case, it would be something like:
class MatchesPlayer extends Model {
protected $_saveData = null;
public function updateScore($data) {
$this->_saveData = $data;
try {
// You can use saveAll to validate
// only, and not actually save
$saved = $this->saveAll($data, array('validate' => 'only'));
} catch (Exception $e) {
// Catch exceptions here in case the
// saveAll is instead something that throws Exceptions
// Or your database uses exceptions
$saved = false;
}
$this->_saveData = null;
return $saved
}
}
You could then use $this->_saveData across the model. If you want to be clever with this, you could detect all sub-models that are being saved in the $data and then set the $this->_saveData on those as well - I would make this an AppModel method of course.
Note that you may want to throw exceptions from this updateScore() method when validation fails. Throwing an exception if validation fails - vs save - would allow you to set a custom flash message for the user as well, or even have an api that responds with a different status code.
Use custom validation rules in MatchesPlayer model, please check
http://book.cakephp.org/2.0/en/models/data-validation.html#adding-your-own-validation-methods

CakePHP 2.0 Accessing model field values for view/controller comparisons - Only letting users edit/delete their own posted items

I am fairly new to CakePHP, I am trying to only allow those users who created an event to be able to edit or delete an event, so I am comparing the current user id, with the 'user_id' field of the event the current event (saved when a user creates an event). Any help would be appreciated thanks, my code(Andrew Perk) is as follows:
public function isAuthorized($user) {
$this->loadModel('User');
if ($user['role'] == 'admin') {
return true;
}
if (in_array($this->action, array('edit', 'delete'))) {
if ($user['id'] != $this->request->data['Event']['user_id']) { ///THIS IS THE LINE I FEEL IS WRONG - PLEASE ADVISE
//echo debug($event['user_id']);
//$this->Session->setFlash(__('You are not allowed to edit someones event'));
return false;
}
}
return true;
}
There are a few ways you can accomplish this. The one I have found that usually works best is to put a callback in the model that will set the user_id for the record you are trying to modify. Then it doesn't have to get all mixed up in controller everywhere you are trying to CRUD a record.
You can read more about limiting user data here: http://blogchuck.com/2010/06/limit-data-by-user-with-cakephp/
This will also apply to deleting data.
Hope it helps. Happy coding!

Receive model data inside a CakePHP model callback function

I try to use existing model data inside a models callback function in CakePHP 2.1 but can't get it working.
What I do is I try to get a users role in the beforeValidate() callback and check if it's empty. If yes, I'll set it. Normally I do it like this, and for the first creation of the record it works pretty well.
if (empty($this->data[$this->alias]['role']))
$this->data[$this->alias]['role'] = 'user';
The problem is, every time an existing record (user) gets updated, the role will be set again.
Question: So, how do I check if the field role is already set in the record data, not the post data (seems like $this->data[$this->alias] only contains POST data)?
There are three solutions to this problem as I can see it.
Set a default value in the database column (easiest, best?)
Add role to your inputs everytime.
Lookup the user and add a role if it's missing
The first two options seem obvious, so I'll just illustrate the last.
public function beforeValidate() {
if (!empty($this->id) {
$this->data[$this->alias][$this->primaryKey] = $this->id;
}
if (!empty($this->data[$this->alias][$this->primaryKey])) {
// user exists, check their data
$id = $this->data[$this->alias][$this->primaryKey];
$user = $this->find('first', array(
'conditions' => array($this->primaryKey => $id)
));
$this->data[$this->alias]['role'] = $user[$this->alias]['role'];
}
if (empty($this->data[$this->alias]['role'])) {
// new user but missing a role
$this->data[$this->alias]['role'] = 'user';
}
return true;
}
This callback will check if an ID was passed, and if so it will look up the user and populate the role field. Then, it checks for an empty role and fills it with the default if necessary. Obviously he more code you have, the more possibilities for bugs, so I suggest the first option for default column values.
try:
if (empty($this->data[$this->alias]['role']) && empty($this->role)) {
$this->data[$this->alias]['role'] = 'user';
}

Cakephp check if model exists

I am creating an application in which I am using two plugins.
For future use I want to check whether the two plugins are being used together or separately.
I need to check if the model exists and if so perform some logic and if not - not.
If I try if($this->loadModel('Model')) { etc }
I get an error saying the model does not exist which is what I want but I don't want an error which prevents the logic from proceeding.
Basically I want:
if(Model->exists()) { do->this }
else { do->somethingelse }
I tried using the php function class_exists() but that returns false regardless of whether the Model exists or not.
I would use App::objects('model') as of 2.x (Not sure when this was implemented).
class AppController extents Controller {
private function _modelExists($modelName){
$models = App::objects('model');
return in_array($modelName,$models);
}
}
//Somewhere in your logic
if($this->_modelExists('SomeModel')){
//do model exists logic
} else {
//do other logic
}
*Note that App::objects('model') will not include models from plugins. You could do:
$models = array_merge(
App::objects('model'),
App::objects('MyPlugin.model')
);
You can also do this with pure php as follows
if(class_exists('SomeModel')){
//do model exists logic
} else {
//do other logic
}
// The pitfall of this approach, is that it will not assure
// that `SomeModel is a decedent of the `Model` class.
You can do this :
$model = ClassRegistry::init("User");
if $model is null this means that the User model does not exist
You can do this from every where in the code

Resources