I am trying to learn CakePHP and I have a simple question,
I have
$this->set('settings', $this->Setting->find('all'));
in a Controller. I want to concentrate this down to:
find all settings where the type is equal to General
How could I do this?
You can use findAllBy function.
findAllBy(string $value, array $fields, array $order, int
$limit, int $page, int $recursive)
You can read this in the CakePHP Cookbook
So your example will look like this:
$this->Setting->findAllByType('General');
$this->Setting->findAllByType($variable); //If you want to use a variable
Or
$this->Setting->find('all', array('conditions' => array('type' => $type)));
Or (NOT Recommended)
$this->Setting->query("SELECT * FROM settings WHERE type = 'General';");
Simply you may still able to use find method of your model object by supplying it with parameters.
$settings = $this->Setting->find('all', array('conditions' => array('type' => 'General')));
$this->set('settings', $settings);
The most important thing for you as a beginner, regarding this question, is to check out this link.
Related
I have a very complex setup on my tables and achieving this via any of the find() methods is not an option for me, since I would need to fix relationships between my tables and I don't have the time right now, so I'm looking for a simple fix here.
All I want to achieve is run a query like this:
SELECT MAX( id ) as max FROM MyTable WHERE another_field_id = $another_field_id
Then, I need to assign that single id to a variable for later use.
The way I have it now it returns something like [{{max: 16}}], I'm aware I may be able to do some PHP on this result set to get the single value I need, but I was hoping there was already a way to do this on CakePHP.
Assuming you have a model for your table and your are using CakePHP 2.x, do:
$result = $this->MyTable->field('id', array('1=1'), 'id DESC');
This will return a single value.
see Model::field()
This example is directly from the CakePHP documentation. it seems you can use the find method of a model to get count
$total = $this->Article->find('count');
$pending = $this->Article->find('count', array(
'conditions' => array('Article.status' => 'pending')
));
$authors = $this->Article->User->find('count');
$publishedAuthors = $this->Article->find('count', array(
'fields' => 'DISTINCT Article.user_id',
'conditions' => array('Article.status !=' => 'pending')
));
i've been trying to learn more about how to have fat models and skinny controllers the right way, because before my models would have basically no code and i'm trying to change that. My function works, but now i'm trying to combine two find() queries that look almost exactly the same except one of them has a simple condition.
My model looks something like this:
function pieChart() {
//Get Data for PieChart
$this->RecordDrug->virtualFields['sum'] ='COUNT(*)';
$records = array();
$records=$this->RecordDrug->find('list',
array('fields' => array( 'Drug.drug', 'sum'),
'contain' => array( 'Drug', 'Record' ),
'group' => 'Drug.Drug'
));
$this->set('output',$records);
return $records;
}
I will have two controllers using this. One of them will use this code as is, just simply call the pieChart() function. The other controller will have to see a condition that only selects the users entries. So
'conditions' => array('Record.user_id' => $this->Auth->user('id'))
How do I go about this the right way? I think i'm having trouble with this because my OOP knowledge is pretty limited. If anyone has any examples or resources that can help me make my find() functions more efficient and streamlined, i'd really appreciate it.
I done that kind of things very simple:
public function myQuery($conditions = null) {
$this->virtualFields['sum'] ='COUNT(*)';
$result = $this->find('all', array('conditions' => $conditions,
'fields' => array('Drug.drug', 'sum'),
'contain' => array('Drug','Record'),
'group' => 'Drug.Drug'
));
return $result;
}
Now you can call this with your argument:
$conditions = array('Record.user_id' => $this->Auth->user('id'));
$data = $this->RecordDrug->myQuery($conditions);
Or without it:
$data = $this->RecordDrug->myQuery();
Note that in this case you need to put myQuery() in to RecordDrug model and you need to use 'all' instead of 'list', because 'list' doesn't support contain option.
So now if you have additional conditions - you just need to pass it in the argument. If you leave it null - it do the query without the conditions statement.
I have a model that runs a query with a bunch of conditions in the SQL. As a result, the model needs to accept a lot of parameters, i.e:
this->model_name->method($param1, $param2, ... )
On the model side, I typically set this up as
function method($param1 = NULL, $param2 = NULL, ... )
Each of those parameters is optional, and use cases will vary around the app. So my question is: at what point (if ever) does it make sense to start passing these parameters to the method via an array, a la:
$params = [
'param1' => 'whatever',
'param2' => 'whatever',
...
]
this->model_name->method($params)
With the end goal being, I suppose, cleaner code, and less instances of method(null, null, null, null, $param) unless that's an okay thing to do.
Most answers have been supportive of the array method (which, generally speaking, I would also agree with), but I'll play devil's advocate and list some negatives:
Documentation is less clear
Most methods of documenting functions/methods will list the parameters of that function individually. For example, a function with a basic DocBlock will look like this:
/**
* A function that accepts an array of params
* #param array $param_array An array of key=>value arguments
*/
function accept_array($param_array = array('key1' => 'first_val', 'key2' => 'second_val')) {
var_dump($param_array);
}
Note how the DocBlock doesn't directly support individual parts of the $param_array, just the array as a whole. In contrast, listing all the arguments individually looks like this:
/**
* A function that 'normal' params
* #param string $key1 First argument
* #param string $key2 Second argument
*/
function accept_normal($key1 = 'first_val', $key2 = 'second_val') {
echo $key1;
echo $key2;
}
This is also a problem if you expect your functions to be fairly self-documenting, as in the first example you're not required to actually list your expected arguments in the function itself.
Default values may not work as expected
'As expected' is probably a bit of a loaded phrase (and this is probably one of the more obvious problems), but take the following:
function accept_array($param_array = array('key1' => 'first_val', 'key2' => 'second_val')) {
var_dump($param_array);
}
accept_array(array('key2' => 'a_different_val'));
Some may expect the var_dump to include the default value of key1 and the new value of key2, but the whole array is replaced, meaning you will need to remember to set default values for each key manually in each function, like so:
function accept_array($param_array = array()) {
if (!isset($param_array['key1'])) { $param_array['key1'] = 'first_val'; }
if (!isset($param_array['key2'])) { $param_array['key2'] = 'second_val'; }
var_dump($param_array);
}
accept_array(array('key2' => 'a_different_val'));
No automatic filtering
Listing the arguments the 'normal' way also gives you a built-in set of filters. Take for example this quick and dirty user search:
/**
* We want to allow searching for users by user_id or email only!
* #param array $param_array
*/
function find_user($param_array = array('user_id' => 0, 'email' => '')) {
foreach ($param_array as $field => $value) {
$this->db->or_where($field, $value);
}
$this->db->get('users');
}
find_user(array('first_name' => 'Joe', 'last_name' => 'Bloggs'));
Without manually adding some 'accepted keys' type validation on the $param_array, a call to the find_user() function can essentially use whatever fields it likes. The simpler version would obviously look like this:
/**
* We want to allow searching for users by user_id or email only!
* #param int $user_id
* #param string $email
*/
function find_user($user_id = 0, $email = '') {
$this->db->or_where('user_id', $user_id);
$this->db->or_where('email', $email);
$this->db->get('users');
}
// No way for me to submit any other fields, they'll just fail when they get to the query
find_user('Joe', 'Bloggs'));
I accept some of these are a bit entry-level and there's probably many more that I missed (feel free to comment with more and I'll copy them into the reply with credit), but hopefully there's enough there to make people think twice about automatically using the 'array method' without thinking about manual validation and documentation etc.
Passing an array of parameters provides a better option for self-documenting your code.
When I use many parameters, I often find myself using a style like:
// do_something_model($enable_option1,$enable_option2,$enable_option3)
do_something_model(FALSE, TRUE, FALSE)
where I carry a comment line with the parameter names to remind myself of how I am
using the model.
In such a case, using an array with meaningfully named keys provides a useful mnemonic.
More recently, I am also using more wrapper functions. For example, I may have my
basic model method do get all my data from a table and this method will have a few
options.
I then define a new method that does a specific task and then invoke the basic method within it using the correct options.
Footnote
I find that if my methods have "too many options", it is better to rethink the purpose of the method and to break it up into two or more specialized methods that are easier to use.
I would recommend the array version as well. Symfony2 also uses this pattern a lot, for instance in rendring templates, creating form classes and creating http responses in general. You just have to make sure you cleanly document all possible parameters.
You could go either route, but an array would definitely keep your methods cleaner. It makes perfect sense to pass the parameters as an array.
I am trying to extract ONLY the PlanDetails where PlanDetail.company_id = Company.id AND PlanDetail.id' => $id.. ( you can see the conditions in my controller below)..
Controller:
function pd_list_by_company($id = null) {
$this->recursive = 2; // I am going to use containable to trim this.
return $this->PlanDetail->find('all',
array('conditions' =>
array('AND' =>
array('PlanDetail.company_id' => 'Company.id',
array('PlanDetail.id' => $id)))));
}
Test View:
$planDetailsByCompany = $this->requestAction('/planDetails/pd_list_by_company');
debug($planDetailsByCompany );
Output result of my debug??
Array()
If I remove the conditions and just have the find all, I get all PlanDetails as expected, so I know the data is being passed.. SQL debug dump even shows the query:
WHERE ((`PlanDetail`.`company_id` = 'Company.id') AND (`PlanDetail`.`id` IS NULL))
And yes, I did notice the $id is NULL, and I know the value needs to be there.. So maybe my question is why is the $id value not being passed to the controller even though I can see the PlanDetail.id value on a find('all') w/ out the conditions??
Thanks for any tips.
Since $id seems to be null, I would assume that you call the function without the parameter. And you don't get an error message, because as far as PHP is concerned the parameter is optional. In this case it's clearly required, so you should make it a required parameter in your function declaration:
function pd_list_by_company($id) {
Also you could simplify the return statement, you do not need the AND:
return $this->PlanDetail->find('all',
array('conditions' =>
array('PlanDetail.company_id' => 'Company.id','PlanDetail.id' => $id)
)
);
To answer the question why is the $id not being passed is because you're not passing it
To pass say $id of 2 you need to do the following in your requestAction
$this->requestAction('/planDetails/pd_list_by_company/2');
Seems to me that your code should just be
return $this->PlanDetail->find('array('PlanDetail.id' => $id));
Assuming you have the $this->PlanDetail->recursive flag set to > 0, your Model should already know about and return the associated data for any 'Company' table.....
I'm used to an old (1.3) version of CakePHP but the find() function is pretty basic and is designed to only return one row.
and yes, you definitely need to call the function with the id appended to the url, eg.
$planDetailsByCompany = $this->requestAction('/planDetails/pd_list_by_company/999');
I happened to be making some changes to a WordPress blog and noticed that they use parse_str (http://php.net/parse_str) for parsing and setting their optional parameters to a function.
I'm wondering if there is an advantage to this over sending an array?
Examples:
With array:
$blahOptions = array(
'option_1' => true,
);
BlahArray($blahOptions);
function BlahArray($options = array()) {
$defaults = array(
'option_1' => false,
'option_2' => 'blah',
);
// this would probably be in a function to be used everywhere
foreach ($defaults as $defaultOption => $defaultValue) {
if (!isset($options[$defaultOption])) $options[$defaultOption] = $defaultValue;
}
}
With parse_str:
$blahOptions = 'option_1=1';
BlahString($blahOptions);
function BlahString($options = '') {
$defaults = array(
'option_1' => false,
'option_2' => 'blah',
);
parse_str($options, $defaults);
$options = $defaults;
}
No. That seems like a ridiculous way to pass functional parameter arguments. I could understand it if you needed to recreate $_GET or $_POST variables or something along those lines, but for parameters to a function? That's code smell right there.
They should be using an array, and then utilizing extract() instead. I've worked with Wordpress before, and my advice is to keep the code at arm's length. It is not an example of model programming.
No. There are more disadvantages than advantages.
When you’re using a single string, you just can pass string values. With an array you can use every PHP data type and every element’s value type is independently of each other.
With parse_str, you can potentially drop in the query string from the URL, and the function will work. If you use an array, and you want to use the query string, you'll have to enumerate everything into an array before calling the function.
I'm not totally convinced it's the best way to go, but I see how it can add a bit of flexibility.