Dynamically add virtual field in cakephp - cakephp

I am using CakePHP 2.1.3; I want to create a virtual field dynamically in a controller. Is it possible?
The problem is when I am trying to find max value in a table it gives me another array from the model array. Please ask if you need more information.
When I am trying to execute the following query,
$find_max_case_count = $this->CaseMaster->find('first', array(
'conditions' => array(
'CaseMaster.CLIENT_ID' => $client_id,
'CaseMaster.CASE_NO LIKE' => '%-%'
),
'fields' => array('max(CaseMaster.CASE_NO) AS MAX_NO')
));
It is giving me an array like:
[0]=> array([MAX_NO]=> 52)
However I want it to be like as:
[CaseMaster] => array([MAX_NO] => 52)

I found a solution. I can make the virtual field at runtime. The code should looks like:
$this->CaseMaster->virtualFields['MAX_NO'] = 0;
Write it just above the find query and the query will remain same as it was written.
This link was helpful to find out the solution.

There is no way (as far as I am knowledgeable) to create virtual fields "on the fly". What virtual fields are is "arbitrary SQL expressions" that will be executed when a find runs through the Model and "will be indexed under the Model's key alongside other Model fields".
What do you need to do with "dynamically created virtual fields"? If you explain what exactly you need to accomplish maybe we can provide a different (even more suitable? :) ) solution? I'd personally be happy to help you.
After you editing your question I can say that what you're getting is the way the array should be returned, this is because of the fields parameter. If you want to get a different structure out of it I suggest applying a callback to format it.
Firstly move the method inside the CaseMaster Model:
public function getMaxCaseCount($client_id){
$data = $this->find('first', array(
'conditions' => array(
'CaseMaster.CLIENT_ID' => $client_id,
'CaseMaster.CASE_NO LIKE' => '%-%'),
'fields' => array('max(CaseMaster.CASE_NO) AS MAX_NO')));
return array_map(array('CaseMaster', '__filterMaxCaseCount'), $data);
}
private function __filterMaxCaseCount($input){
//This is just an example formatting
//You can do whatever you would like here.
$output['CaseMaster'] = $input[0];
return $output;
}
The array_map function will apply the __filterMaxCaseCount callback method so that when you call:
$this->CaseMaster->getMaxCaseCount($client_id);
from your controller you will get the data in the way you need it. The array_map function could also look like this:
return array_map(array($this, '__filterMaxCaseCount'), $data);
because you're in the same Class.

Just adding your model alias to field definition also works for this purpose
'fields' => array('max(CaseMaster.CASE_NO) AS CaseMaster__MAX_NO')

Related

Ways to use array in cakephp

Hello I am having a tought time figuring out how to use arrays in cakephp. right now i have a view with 2 columns, active and startYear. i need to grab the start years for all of the columns in the view and sho i have this code.
public function initialize(array $config)
{
$this->setTable('odb.SchoolYear');
}
controller
public function index()
{
$deleteTable = $this->loadModel('DeletedTranscripts');
$this->$deleteTable->find('all', array(
'conditions' => array(
'field' => 500,
'status' => 'Confirmed'
),
'order' => 'ASC'
));
$this->set('startYear',$deleteTable );
}
once i have the array captured and put into lets say startYear can in input a statement like this into my dropdown list to populate it?
<div class="dropdown-menu">
<a class="dropdown-item" href="#"><?= $delete->startYear; ?></a>
</div>
i have been looking for answers for quite awhile any help would be awesome.
Couple of things:
Loading Tables in CakePHP
For this line:
$deleteTable = $this->loadModel('DeletedTranscripts');
While you can get a table this way, there's really no reason to set the return of loadModel to a variable. This function sets a property of the same name on the Controller, which almost correctly used on the next line. Just use:
$this->loadModel('DeletedTranscripts');
Then you can start referencing this Table with:
$this->DeletedTranscripts
Additionally, if you're in say the DeletedTranscriptsController - the corresponding Table is loaded for you automatically, this call might be unnecessary entirely.
Getting Query Results
Next, you're close on the query part, you've can start to build a new Query with:
$this->DeletedTranscripts->find('all', array(
'conditions' => array(
'field' => 500,
'status' => 'Confirmed'
),
'order' => 'ASC'
));
But note that the find() function does not immediately return results - it's just building a query. You can continue to modify this query with additional functions (like ->where() or ->contain()).
To get results from a query you need to call something like toArray() to get all results or first() to get a single one, like so:
$deletedTranscriptsList = $this->DeletedTranscripts->find('all', array(
'conditions' => array(
'field' => 500,
'status' => 'Confirmed'
),
'order' => 'ASC'
))->toArray();
Sending data to the view
Now that you've got the list, set that so it's available in your view as an array:
$this->set('startYear', $deletedTranscriptsList );
See also:
Using Finders to Load Data
Setting View Variables
I also noticed you've had a few other related questions recently - CakePHP's docs are really good overall, it does cover these systems pretty well. I'd encourage you to read up as much as possible on Controller's & View's.
I'd also maybe suggest running through the CMS Tutorial if you've not done so already, the section covering Controllers might help explain a number of CakePHP concepts related here & has some great working examples.
Hope this helps!

CakePHP append to Containable/Contain

How to append to contain when it's already declared?
I call a regular contain/find:
// A controller
$this->Site->contain(array('User'));
$site = $this->Site->find();
I want to automatically add something to contain. I was thinking of doing this in the Model by adding to the find function... something like this:
// Site.php Model
function find($conditions = null, $fields = array(), $order = null, $recursive = null) {
if(!isset($this->containVariable) || !in_array('Box', $this->containVariable)) {
$this->containVariable[] = 'Box';
}
parent::find($conditions, $fields, $order, $recursive);
}
To make this work (automatically adding model Box to contain) I only need to change $this->containVariable by a function or variable that has an array of what's already in contain. In this case this would return array('User'). How to append to contain when it's already declared? Is there a variable that contains contain?
Unless someone can find another solution I've came to the conclusion that:
Unfortunately what I was trying to do seems impossible how containable is designed. I had to manually add my model to every ->contain() calls.
I fixed perhaps a similar issue with persisting contain for multiple find calls (using cakephp 1.3). After declaring contain for a certain model, this will be set for that model:
$this->Behaviors->Containable->runtime
-where $this is some model object. 'runtime' is an array that essentially holds the contain information. So you are close I believe, but instead of:
$this->containVariable[] = 'Box'
You would have:
$this->Behaviors->Containable->runtime['someModel']['contain'][] = 'Box'
To be able to specify more than just the model though, you would have to set up to handle like the following (perhaps build a method accordingly):
$this->Behaviors->Containable->runtime['someModel']['contain'][] = $model_contain_array
$model_contain_array is just an arbitrary name I just chose for an array which holds the contain information you would like to add for a particular model. An example of $model_contain_array might be:
array('modelName' => array(
'fields' => array('id', 'other'),
'otherModelName' => array(
'fields' => array('id', 'otherfield', 'etc')
)
)

Can a virtualfield be based upon linked data in cakephp?

Is it possible for a virtualFields var to be the sum of a field from a linked table?
For example, in, say, an Invoice model, could you have
public $virtualFields = array(
'invoiceNett' => 'SUM(InvoiceLine.nett)'
);
but obviously only SUMming the lines that belong to that invoice?
Thanks.
== Using CakePHP 2.0
You could use the afterFind callback to get the sum. This avoids storing the calculated values​​, which should be avoided when possible.
function afterFind($results)
{
foreach($results as &$result)
{
/*
Use something like:
$this->InvoiceLine->find('all', array('fields' => array('SUM(InvoiceLine.nett) as total'),
'conditions' => array('invoice_id' => $result['Invoice']['id'])));
*/
}
unset($result);
}
As far as I know, the best way to do that would be to have an actual total field, and update it anytime the data is saved (likely with a afterSave callback method).
So - anytime an InvoiceLine is saved, you run some code to update it's associated Invoice with a new total.
//InvoiceLine model
public function beforeSave() {
//code to update Invoice's "total" field
}
Theoretically, yes, if that linked table is an associate that is joined (belongsTo and hasOne).
However this would be a poor idea because if you decide not to include that table you would generate a SQL error.
You'd be better off having a separate function grab the data or creating a virtual field that was a nested SQL query.
Defining your virtual field in the respective Model would make more sense.
If not done so, you will be breaking the MVC pattern.
You can use that virtual field from other related models.
If you don't want using them in all related models, you can always use the field
attribute whilst defining relations.
public $hasMany = array(
'IwantVirtualField' => array(
'className' => 'MyModel',
...
)
);
In a model where you don't want virtual field
public $belongsTo = array(
'IwantVirtualField' => array(
'className' => 'MyModel1',
'fields' => array('MyModel1.id', 'MyModel1.name')
...
)
);

$this->find not working in cakePHP

I have a model with the name Deal class Deal extends AppModel
Now in my controller I call a method in the Deal model called getDeal()
$dealInfo = Deal::getDeal($dealID);
I want the info returned to me but the var_dump displays blank
function getDeal($dealID){
$deal = $this->Deal->find('first', array(
'conditions' =>
'Deal.id' =>$dealID
) ,
'fields' => array(
'Deal.id'
) ,
'recursive' => 1,
));
}
This is the first time I'm working in cakePHP, so this question might sound a bit dumb
When you're just using an id as your find condition you can use CakePHP's dynamic finder methods.
Dynamic finders work like this.
$this->Model->findByModelFieldName();
In your example it would be
$this->findById($dealId, array(
'fields' => array('Deal.id'),
'recursive' => 1
));
I don't know if I'm just being mental, but is this simply because you're not returning anything from getDeal()?
Try adding return $deal; and see if that helps. Your question doesn't state exactly where you're doing the var_dump so I might be well off.
Edit:
As per the discussion with you and 8vius, we've established that this isn't right, and you simply need to change $this->Deal->find() to $this->find() because its being run from the model.
Check your function declaration, $deal_id does not exist and as you can see from the parameter you pass to the function which should me $deal_id and not dealID. So you have a misdeclared function calling find with a variable that does not exist.

CakePHP AutoComplete Question

I am working on a book review application and I am using autoComplete to search for titles in when creating a review. The review model has an associated book_id field and the relationship is setup as the review hasOne book and a book hasMany reviews.
I am trying to pass the Book.id (into the book_id field), but I want to display Book.name for the user to select from. With the default setup (accomplished via CakePHP's tutorial), I can only pass Book.name. Is it possible to display the name and pass the id?
Also, I am passing it via the following code in the create() action of the review controller:
$this->data['Review']['book_id'] = $this->data['Book']['id'];
Is that the proper way to do it in CakePHP? I know in Ruby on Rails, it is automatic, but I can't seem to make it work automagically in CakePHP. Finally, I am not using the generator because it is not available in my shared hosting environment... so if this is the wrong way, what do I need other than associates in my models to make it happen automatically?
Thanks for the help and I promise this is my question for awhile...
UPDATE- I tried the following, but it is not working. Any ideas why?
function autoComplete() {
$this->set('books', $this->Book->find('all', array(
'conditions' => array(
'Book.name LIKE' => $this->data['Book']['name'].'%'
),
'fields' => array('id','name')
)));
$this->layout = 'ajax';
}
The problem is that when I use the code above in the controller, the form submits, but it doesn't save the record... No errors are also thrown, which is weird.
UPDATE2:
I have determine that the reason this isn't working is because the array types are different and you can't change the array type with the autoComplete helper. As a workaround, I tried the follow, but it isn't working. Can anyone offer guidance why?
function create() {
if($this->Review->create($this->data) && $this->Review->validates()) {
$this->data['Review']['user_id'] = $this->Session->read('Auth.User.id');
$this->Book->find('first', array('fields' => array('Book.id'), 'conditions' => array('Book.name' => $this->data['Book']['name'])));
$this->data['Review']['book_id'] = $this->Book->id;
$this->Review->save($this->data);
$this->redirect(array('action' => 'index'));
} else {
$errors = $this->Review->invalidFields();
}
}
FINAL UPDATE:
Ok, I found that the helper only takes the find(all) type or array and that the "id" field wasn't passing because it only applied to the autoComplete's LI list being generated. So, I used the observeField to obtain the information and then do a database lookup and tried to create a hidden field on the fly with the ID, but that didn't work. Finally, the observeField would only take the characters that I put in instead of what I clicked, due to an apparent Scriptaculous limitation. So, I ended up going to a dropdown box solution for now and may eventually look into something else. Thanks for all of the help anyway!
First of all, $this->data will only contain ['Book']['id'] if the field exists in the form (even if it's hidden).
To select something by name and return the id, use the list variant of the find method, viz:
$selectList = $this->Book->find('list', array(
'fields' => array(
'id',
'name'
)));
$this->set('selectList', $selectList);
In the view, you can now use $selectList for the options in the select element:
echo $form->input('Book.id', array('type' => 'hidden'));
echo $form->input('template_id', array(
'options' => $selectList,
'type' => 'select'
));

Resources