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
Related
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.
Please help, this is my first plugin I'm writing and I'm completely lost. I'm trying to write and update information in a table in a joomla database using my custom giveBadge() function. The functions receives two different variables, the first variable is the $userID and the second one is the digit 300 which I pass at the bottom of the class using giveBadge(300). At the same comparing the $userID in the Joomla database to ensure that the number 300 is given to the current user logged in the Joomla site.
Thanks in advance.
<?php
defined('JPATH_BASE') or die;
class plgUserBadge extends JPlugin
{
public function onUserLogin () {
$user =& JFactory::getUser();
$userID =& user->userID;
return $userID;
}
public function giveBadge ($userID, &$badgeID) {
// Get a db connection.
$db = JFactory::getDbo();
// Create a new query object.
$query = $db->getQuery(true);
// Fields to update.
$fields = array(
'profile_value=\'Updating custom message for user 1001.\'',
'ordering=2');
// Conditions for which records should be updated.
$conditions = array(
'user_id='.$userID,
'profile_key=\'custom.message\'');
$query->update($db->quoteName('#__user_badges'))->set($fields)->where($conditions);
$db->setQuery($query);
try {
$result = $db->query();
} catch (Exception $e) {
// Catch the error.
}es = array(1001, $db->quote('custom.message'), $db->quote('Inserting a record using insert()'), 1);
}
}
giveBadge(300); //attaches to $badgeID
?>
Here is not going well with your code:
You can drop the assign by reference in all your code (&) - you really don't need it, in 99% of the cases.
Use an IDE (for example Eclipse with PDT). At the top of your code you have & user->userID; Any IDE will spot your error and also other things in your code.
Study existing plugins to understand how they work. Here is also the documentation on plugins.
The method onUserLogin() will automatically be called by Joomla when the specific event is triggered (when your plugin is activated). Check with a die("My plugin was called") to see if your plugin is really called
inside onUserLogin() you do all your business logic. You are not supposed to return something, just return true. Right now your method does absolutely nothing. But you can call $this->giveBadge() to move the logic to another method.
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
I have made a custom afterFind function in a model, but I just want it to execute it if NOT in admin mode.
public function afterFind($results) {
if(Configure::read('Routing.admin')){
return $results;
}else{
return $this->locale(&$results);
}
}
But it doesn't seems to work. I'm thinking this might not be possible. Any idea?
checking on the core Configure settings doesnt make sense to me.
besides the fact that that 'Routing.admin' is deprecated - its Prefix.admin.
it only stores the prefixes that cake uses.
If you really want to you can store the information in configure::read() in beforeFilter() of your AppController and read it from your model again.
But it would need to something that does not conflict with your settings.
So if you use Prefix you probably could use Routing again:
//beforeFilter - prior to any model find calls!
$isAdmin = !empty($this->params['admin']);
Configure::write('Routing.admin', $isAdmin);
the other option you always have is to pass the information on to the model.
Router::getParam('prefix', true) gives you current request prefix value.
public function afterFind($results, $primary = false) {
return Router::getParam('prefix', true) == 'admin' ? $results : $this->locale(&$results);
}
Tested with Cake 2.4.
If I have a person model with first_name and last_name, how do I create and display a full_name? I would like to display it at the top of my Edit and View views (i.e. "Edit Frank Luke") and other places. Simply dropping echoes to first_name and last_name isn't DRY.
I'm sorry if this is a very simple question, but nothing has yet worked.
Thank you,
Frank Luke
Edit for clarity: Okay, I have a function on the person model.
function full_name() {
return $this->Person->first_name . ' ' . $this->Person->last_name;
}
In the view, I call
echo $person['Person']['full_name']
This gives me a notice that I have an undefined index. What is the proper way to call the function from the view? Do I have to do it in the controller or elsewhere?
If what you are wanting is just to display a full name, and never need to do any database actions (comparisons, lookups), I think you should just concatenate your fields in the view.
This would be more aligned with the MVC design pattern. In your example you just want to view information in your database in a different way.
Since the action of concatenating is simple you probably don't save much code by placing it in a separate function. I think its easiest to do just in the view file.
If you want to do more fancy things ( ie Change the caps, return a link to the user ) I would recommend creating an element which you call with the Users data.
The arrays set by the save() method only return fields in the datbase, they do not call model functions. To properly use the function above (located in your model), you will need to add the following:
to the controller, in the $action method:
$this->set( 'fullname', $this->Person->full_name();
// must have $this-Person->id set, or redefine the method to include $person_id
in the view,
echo $fullname;
Basically, you need to use the controller to gather the data from the model, and assign it to the controller. It's the same process as you have before, where you assign the returned data from the find() call to the variable in the view, except youre getting the data from a different source.
There are multiple ways of doing this. One way is to use the afterFind-function in a model-class.
See: http://book.cakephp.org/view/681/afterFind.
BUT, this function does not handle nested data very well, instead, it doesn't handles it al all!
Therefore I use the afterfind-function in the app_model that walks through the resultset
function afterFind($results, $primary=false){
$name = isset($this->alias) ? $this->alias : $this->name;
// see if the model wants to attach attributes
if (method_exists($this, '_attachAttributes')){
// check if array is not multidimensional by checking the key 'id'
if (isset($results['id'])) {
$results = $this->_attachAttributes($results);
} else {
// we want each row to have an array of required attributes
for ($i = 0; $i < sizeof($results); $i++) {
// check if this is a model, or if it is an array of models
if (isset($results[$i][$name]) ){
// this is the model we want, see if it's a single or array
if (isset($results[$i][$name][0]) ){
// run on every model
for ($j = 0; $j < sizeof($results[$i][$name]); $j++) {
$results[$i][$name][$j] = $this->_attachAttributes($results[$i][$name][$j]);
}
} else {
$results[$i][$name] = $this->_attachAttributes($results[$i][$name]);
}
} else {
if (isset($results[$i]['id'])) {
$results[$i] = $this->_attachAttributes($results[$i]);
}
}
}
}
}
return $results;
}
And then I add a _attachAttributes-function in the model-class, for e.g. in your Person.php
function _attachAttributes($data) {
if (isset($data['first_name']) && isset($data['last_name'])) {
$data['full_name'] = sprintf("%s %s %s", $data['first_name'], $data['last_name']);
}
return $data;
}
This method can handle nested modelData, for e.g. Person hasMany Posts then this method can also attachAttributes inside the Post-model.
This method also keeps in mind that the linked models with other names than the className are fixed, because of the use of the alias and not only the name (which is the className).
You must use afterFind callback for it.
You would probably need to take the two fields that are returned from your database and concatenate them into one string variable that can then be displayed.
http://old.nabble.com/Problems-with-CONCAT-function-td22640199.html
http://teknoid.wordpress.com/2008/09/29/dealing-with-calculated-fields-in-cakephps-find/
Read the first one to find out how to use the 'fields' key i.e. find( 'all', array( 'fields' => array( )) to pass a CONCAT to the CakePHP query builder.
The second link shows you how to merge the numeric indexes that get returned when you use custom fields back into the appropriate location in the returned results.
This should of course be placed in a model function and called from there.