CakePHP magic findBy method for comparisons - cakephp

We have plots and beans that can be planted into the plots.
I am absolutely determined to use the following to find all plots the owner has, with a bean inside them.
$plots = $this->Plot->findAllByOwnerAndBean_id(uid, '> 0');
However, it gives me the SQL WHEREPlot.owner= '15' ANDPlot.bean_id= '> 0'
This suggests it may be impossible, but I don't feel it's definitive. (potentially, even relevant as 2.2?) And it may be, so the question is two-fold:
How do I get what I want out of the findBy, and if I really can't, how could I avoid less code than the following, which I can confirm works?
$plots = $this->Plot->find('all', array(
'conditions' => array(
'owner' => uid,
'bean_id >' => 0
)
));

I don't see how it would be possible with magic methods (may work with DboSource::expression() but if its user input you'd have to sanitize it yourself). You can, however, just make a helper method in your model.
class Plot extends AppModel {
public function findAllByOwnerAndBeanId($owner, $beanId) {
return $this->find('all', array(
'conditions' => array(
'owner' => $owner,
'bean_id >' => $beanId,
),
));
}
}
Edit: You may instead try the following, but note that it's not tested.
$ds = $this->Plot->getDataSource();
$plots = $this->Plot->findAllByOwnerAndBean_id($uid, $ds->expression('> ' . intval($userInputtedBeanId)));
May be better for Sanitize::escape() rather than intval.

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 Setting recursive for select joins

I create a search on models like this:
$options = array(
'conditions' => array(
'CompletedSurvey.' . $this->CompletedSurvey->primaryKey => $id
),
'recursive' => 5
);
$survey = $this->CompletedSurvey->find('first', $options);
The way I have my models set up, this will return five models (do to their various joins) each recursed up to 5 times (if available). The problem is that I only want one of these models to be recursed X5. The others don't need to be.
Is there a way to tell the find function which tables to recurse and at what level to recurse them to? So, telling cake which models to recurse and at what level for each one?
Please read the CakePHP cookbook regarding the recursive property on models, as it does not work the way you are thinking. http://book.cakephp.org/2.0/en/models/model-attributes.html#recursive
What you're looking for is the containable behavior, where you can specify exactly which models to return. Please see the cookbook on how to use containable. http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
Well, this is not exactly recursive, but the only way to access deep relations and avoid to use recursive on a main model is to use containable behavior:
$options = array(
'conditions' => array(
'CompletedSurvey.' . $this->CompletedSurvey->primaryKey => $id
),
'contain' => array(
'SomeModel.SomeOtherModel.AnotherModel.AnotherModel'
)
);
$survey = $this->CompletedSurvey->find('first', $options);
And don't forget to set containable behavior for CompletedSurvey model!
class CompletedSurvey extends AppModel {
public $actsAs = array('Containable');
}

HABTM with self requires 2x the rows in join table?

I'm trying to build a CMS with Nodes as the main model. Each Node belongsTo a NodeType, and every Node can be related to any/every other Node.
So - thought this called for HABTM:
//Node model
public $hasAndBelongsToMany = array(
'AssociatedNode' => array(
'className' => 'Node',
'foreignKey' => 'node_id',
'associationForeignKey' => 'associated_node_id',
'joinTable' => 'node_associations'
)
);
The problem is, it seems like the only way it works is if I have TWO rows for each association.
Example with just one association row:
Nodes
ER (id=1)
George Clooney (id=2)
A single row in the join-table describing the relationship between those two nodes:
'node_id' = 1
'associated_node_id' = 2
Now - If I query for TV Shows and Contain it's Actor nodes:
$nodes = $this->Node->find('all', array(
'conditions' => array(
'Node.node_type_id' => '645' //tv shows
),
'contain' => array(
'AssociatedNode' => array(
'conditions' => array(
'AssociatedNode.node_type_id' => '239' //actors
),
)
)
));
This works, and I get ER -> George Clooney.
But - what if I want to pull all shows that George Clooney is in?
$nodes = $this->Node->find('all', array(
'conditions' => array(
'Node.node_type_id' => '239' //actors
),
'contain' => array(
'AssociatedNode' => array(
'conditions' => array(
'AssociatedNode.node_type_id' => '645' //tv shows
),
)
)
));
This doesn't work because it's looking for George Clooney's ID to be in the 'node_id' field, and ER's ID to be in the 'associated_node_id' field - when in reality they're reversed.
The only solution I've thought of is to keep two rows for EVERY association. But this seems overkill. But then I have to come up with some kind of custom something that makes sure to keep each duplicate in sync w/ the other every time an association is saved or deleted...etc - and this seems like a large can of worms.
Is there something I'm missing?
You could probably do it with a custom query, but to keep with standard Cake functions, one thing I can think of would be to declare two relationships between the Nodes:
public $hasAndBelongsToMany = array(
'AssociatedNode1' => array(
'className' => 'Node',
'foreignKey' => 'node_id',
'associationForeignKey' => 'associated_node_id',
'joinTable' => 'node_associations'
),
'AssociatedNode2' => array(
'className' => 'Node',
'foreignKey' => 'associated_node_id',
'associationForeignKey' => 'node_id',
'joinTable' => 'node_associations'
)
);
and then you could merge both arrays in an afterFind callback.
function afterFind($results)
{
foreach($results as &$result)
{
if(isset($result['AssociatedNode1']) || isset($result['AssociatedNode2']))
{
$associated_nodes = array();
if(isset($result['AssociatedNode1']))
{
foreach($result['AssociatedNode1'] as $associated_node)
{
$associated_nodes[] = $associated_node;
}
}
if(isset($result['AssociatedNode2']))
{
foreach($result['AssociatedNode2'] as $associated_node)
{
$associated_nodes[] = $associated_node;
}
}
$result['AssociatedNode'] = $associated_nodes;
}
}
unset($result);
return $results;
}
But this would force you to declare both AssociatedNode1 and AssociatedNode2 in the call to contain();
I'm not sure what the details of your use case are, but I've got a couple of alternate options for you:
You might look into using the Tree Behavior - this is built for storing things in trees, which it sounds like might be what you're doing. I haven't used it myself, so I'm not sure how applicable it is to your use.
On the other hand, if you store the relationships in a consistent direction (i.e. always TV Show->Actor) and know which direction your queries run (looking up the tree for TV Shows an Actor is in vs looking down for finding Actors in a TV Show), you should be able query AssociatedNode when you're going the reverse direction, e.g.
$nodes = $this->AssociatedNode->find('all', array(
'conditions' => array(
'AssociatedNode.node_type_id' => '239' //actors
),
'contain' => array(
'Node' => array(
'conditions' => array(
'Node.node_type_id' => '645' //tv shows
),
)
)
));
In this case, it might be better to use "ChildNode" instead of "AssociatedNode" for clarity.
But again, both of these answers depend on the particulars of your use case - nIcO's answer is a good general solution. It is (necessarily) awkward and possibly slower, but it abstracts away the awkwardness nicely.
One thing I've done in the past that might help is to make a model for the join table. I was able to store extra data in there and do whatever I wanted with my queries. Then on both sides of that join model just define a hasMany association (maybe a belongsTo as well). Then you can do a find using the join model and write something like (from a controller):
$this->Node->NodesNode->find('all', array('conditions'=>array("or"=>array('node_id'=>$id,'sub_node_id'=>$id))));
IMHO: there is nothing really forcing you to use cake conventions. I love cake, but sometimes both it and the ORMs complicate really easy things. You may just want to write your own query and parse the results yourself. It'd probably be faster than dealing with the overhead of another behavior or model plus you could probably write a way better query than the defaults given.
Oh and lastly, I'd watch out when you are using 1 model for multiple purposes. Really think if that one model should really be supporting everything. I've found that whenever I've done that I've just rewritten the entire thing within a year or two. You will quickly hit a bottle neck where some nodes need extra behavior in this way, others need something else and you have if statements (or maybe something more clever) scattered everywhere. Plus it really slows thing down to do crazy tree based queries in a db.

Complex find statement, don't know how to write multiple values for one condition

I am trying to build a paginated find call to my Unit model. I need the condition to be that it looks for unit.type of condo and rentalco, house and rentalco, but NOT rentalco and hotel. Additionally, the way I have my code worded, cake only returns unit types that are rentalco.
public function view($type=null) {
$this->set('title', 'All '.$type.' in and near Gulf Shores');
$this->set('featured', $this->Unit->getFeatured());
$this->paginate['Unit']=array(
'limit'=>9,
'order' => 'RAND()',
'contain'=>array(
'User'=>array('id'),
'Location',
'Complex',
'Image'
),
'conditions'=>array(
'Unit.type'=>array($type, 'rentalco'),
'Unit.active'=>1)
);
$data = $this->paginate('Unit');
$this->set('allaccommodations', $data);
$this->set('type', $type);
}
UPDATE I figured out why my find statement wasn't working (just had been passing the word condos instead of condo into my browser bar....derp derp); however, I would still love to know how I can tell cake to NOT allow a find with both type hotel and rentalco.
You are looking for the NOT. It would be something like:
'conditions' => array(
'NOT' => array('Unit.type' => array('hotel', 'rentalco')),
),
To be more specific, I would need to see your model schema.

CakePHP find() not working accross models

I am having a very curious problem. I am trying to do a find with conditions that work across model relationships. To wit...
$this->Model->find('first', array(
'conditions' => array(
'Model.col1' => 'value',
'RelatedModel.col2' => 'value2')));
...assuming that Model has a hasMany relationship to RelatedModel. This particular find bombs out with the following error message:
Warning (512): SQL Error: 1054: Unknown column 'RelatedModel.col2' in 'where clause' [CORE/cake/libs/model/datasources/dbo_source.php, line 525]
Looking at the SELECT being made, I quickly noticed that the comparison in the related model was in fact being placed in the WHERE clause, but for some reason, the only thing in the FROM clause was Model, with no sign of RelatedModel. If I remove the comparison that uses the relationship, related models ARE pulled in the result.
I'm using Cake 1.2.4. At first glance, there's nothing in the 1.2.4 -> 1.2.5 changelog that I see that covers this, and you would think that such an obvious bug would be hunted down and fixed a few days later, as opposed to waiting a full month and not mentioning anything in the release annoucement.
So, uh, what's going on?
If your models are using the Containable behavior, make sure you contain those models.
First, in your {model_name}.php file:
class {ModelName} extends AppModel {
var $actsAs = array('Containable');
}
Then in your find:
$results = $this->Model->find('first', array(
'conditions' => array(
'Model.col1' => 'value',
'RelatedModel.col2' => 'value2',
),
'contain' => array('RelatedModel'),
));
If not using Containable behavior, then try explicitly increasing the recursion level:
$results = $this->Model->find('first', array(
'conditions' => array(
'Model.col1' => 'value',
'RelatedModel.col2' => 'value2',
),
'recursive' => 1,
));
Note that the latter method will more than likely retrieve a lot of unnecessary data, slowing down your application's speed. As such, I highly recommend implementing the use of the Containable behavior.

Resources