CakePHP: find neighbors, order on 'name' or 'order' - cakephp

I have a list of ordered items, ordered according to the int field order.
I'm creating a gallery in CakePHP 1.2 that has a prev and next button and those should link to the previous and next item according to their ordering, not according to their id.
In order to get this result I've included the 'order' parameter to the find function, and populated it with 'Item.order'=>'DESC'. Still the result is an id ordered list.
My question is: what do I do wrong?
My controller:
$this->Item->id = 16;
$neighbours = $this->Item->find('neighbors', array(
'order' => array('Item.order'=>'DESC'),
'fields' => array('id','name')
));
My Solution
I've tried a different approach. My code now does the job and looks as follows:
$order = $this->Item->findById(6);
$neighbours = $this->Item->find('neighbors', array(
'field' => 'order',
'value' => $order['Item']['order']
));
By setting the parameter 'field' to the field will be the ordering field, and set the 'value' parameter to the order value of you current Item you'll get the prev and next.

Yeah the problem was that you weren't including the order field in your fields array.
$neighbours = $this->Item->find('neighbors', array(
'order' => 'order DESC',
'fields' => array('id', 'name', 'order')
));
Unless you have related models with conflicting field names you don't need to include the Item. model prefix (though I usually do anyway to avoid such errors.) You're original syntax would work if you had included [Item.]order in "fields"
Finally, your solution is not optimal, you're making two SQL queries when you don't need to. and 'field' is not a query option as far as I'm aware which actually means you're returning all of the fields in the table.

I was having problems with this. Basically I have a list of questions that need to be randomly ordered (once only) per user session.
I was setting the order of the model to something like this:
'FIELD(TestQuestion.id, 3, 1, 5)';
This worked fine for normal queries, but finding neighbors is stymied by line 2897 in Model.php:
$query['order'] = $field . ' DESC';
So to get around it, I did the following:
Add a virtual field called 'examination_order', and set the order to that:
$this->TestQuestion->virtualFields['examination_order'] = 'FIELD(TestQuestion.id, 3, 1, 5)';
$this->TestQuestion->order = array(
$this->TestQuestion->alias . '.examination_order'
);
In the find neighbors call, set the 'field' to 'examination_order', with the value set to the index as found previously:
$neighbors = $this->TestQuestion->find(
'neighbors',
array(
'field' => 'examination_order',
'value' => $testQuestion[$this->TestQuestion->alias]['examination_order'],
'conditions' => $conditions
)
);

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 2.4.4 How can I structure this find() with Containable?

Tables
User(id)
Profile(id, *user_id*, type)
Attribute(id, *attribute_name_id*, value)
AttributeName(id, name)
ProfileAttribute(id, *profile_id*, *attribute_id*)
Relationships
The relationships are set up correctly (and go both ways, hasMany/belongsTo).
User hasMany Profile
Profile hasMany ProfileAttribute
Attribute hasMany ProfileAttribute
(could be written Profile hasMany Attribute through ProfileAttribute)
AttributeName hasMany Attribute
Goal
For a specified User id, with a find() in the User model, I only want the following fields, laid out as such:
$results[Profile.type][AttributeName.name][Attribute.value]
Is it even possible to retrieve results arranged like this? I've been playing around with Find and Containable for hours, but, first time trying to do anything complicated like this with Cake, I can't get the hang of it.
Thanks!
EDIT
I'm getting these results now, all that I need, but nowhere near the desired format above -can it be done as part of the find, or does it need to be sorted after?
Yep, it's possible. You just have to specify fields on containable:
$this->User->find('all', array(
'conditions' => array('User.id' => $id),
'fields' => array('id'),
'contain' => array(
'Profile' => array(
'fields' => array('id','type'),
'ProfileAttribute' => array(
'fields' => array('id'),
'AttributeName' => array(
'fields' => array('id','name'),
'Attribute' => array(
'fields' => array('id','value')
)
)
)
)
)
);
Be wary that when you use contain and fields options, you have to specify the id so it can make the association (check the docs)
EDIT: I don't know if you can group contained data as the docs didn't say anything about that, but probably you can, as they accept some parameters as in the main query. You can try it, adding group to any contained data that you want to group

CakePHP -- conditions ignored when using paginate() twice in a single action

I have a photoblog built on CakePHP 2.0 with a data structure that looks like:
POSTS <-habtm-> TAGS <-habtm-> IMAGES
I am building an AJAX-based feature to find all blog posts and images that match a given tag. Page 1 of Posts and page 1 of Images are loaded into adjacent panels when a tag is first selected. After that, they can be paged through independently. For the most part this is working fine, except when I am fetching the initial pages of data.
I am using paginate() twice in the first action -- once to get my Posts and a second time to get the Images. The problem is that the conditions I assign to paginate() for the second model in the sequence are completely ignored. Individually they both work fine, and switching their order has confirmed it's a sequence-dependent problem for me, rather than restricted to one of the models or the other.
I've searched to see if anyone else has encountered similar problems in the past, but this is either an unusual design choice on my part or I'm not finding the right search query.
My basic $paginate array is declared as follows in my TagsController.php:
public $paginate = array(
"PostsTag" => array(
"limit" => 4,
"order" => "Post.id DESC",
"contain" => array(
"Tag",
"Post" => array("fields" => array(
"id", "title", "created"))
),
"group" => array("Post.id")
),
"ImagesTag" => array(
"limit" => 4,
"order" => "Image.id DESC",
"contain" => array(
"Tag",
"Image" => array("fields" => array(
"id", "title", "url", "created", "gallery"))
),
"group" => array("Image.id")
)
);
From my main search action I call two private functions:
$posts = $this->post_pagination($tagIds);
$images = $this->image_pagination($tagIds);
which add the limiting conditions to $paginate and look like this:
private function post_pagination($tags, $page = 1) {
$this->paginate['PostsTag']['conditions'] = array(
"status" => 1,
"OR" => array("tag_id" => $tags)
);
$this->paginate['PostsTag']['page'] = $page;
return $this->paginate("PostsTag");
}
private function image_pagination($tags, $page = 1) {
$this->paginate['ImagesTag']['conditions'] = array(
"gallery" => 1,
"OR" => array("tag_id" => $tags)
);
$this->paginate['ImagesTag']['page'] = $page;
return $this->paginate("ImagesTag");
}
Cake is respecting limit, order, contain, etc. without issue, but drops the ball on conditions specifically for whichever model I try to paginate over second. It feeds me back the first 4 results ordered properly, but completely unfiltered. I do not think my somewhat complicated conditions are at fault either -- as long as I don't break syntax, I can type completely random strings into conditions for the second paginate() and get back identical results.
Any help or suggestions are greatly appreciated.
[edit] Here is an SQL dump of the second paginate() query:
SELECT `PostsTag`.`id`, `PostsTag`.`post_id`, `PostsTag`.`tag_id`,
`Tag`.`id`, `Tag`.`name`, `Post`.`id`, `Post`.`title`, `Post`.`created`
FROM `posts_tags` AS `PostsTag`
LEFT JOIN `tags` AS `Tag` ON (`PostsTag`.`tag_id` = `Tag`.`id`)
LEFT JOIN `posts` AS `Post` ON (`PostsTag`.`post_id` = `Post`.`id`)
WHERE 1 = 1
GROUP BY `Post`.`id`
ORDER BY `Post`.`id`
DESC LIMIT 4
As you can see, Cake is generating a WHERE 1 = 1 in place of my conditions.
DEAR PEOPLE FROM THE FUTURE: Here's what we've figured out so far...
OP is correct that YourController::$paginate is only fed into the PaginatorComponent once. If you need to call YourController::paginate() again with different options, you'll need to unload the component first, e.g.:
$this->Components->unload('Paginator');
Then, the next time you call YourController::paginate(), it will reload whatever's in the YourController::$paginate property.
So upon some more poking around I discovered the following:
Any alterations made to $paginate after an initial paginate() call is made are not carried through to the Paginator component. This applies to conditions, order, limit, etc.
So doing this:
$this->paginate['<model1>']['conditions'] = array( ... );
$model1Results = $this->paginate("<model1>");
$this->paginate['<model2>']['conditions'] = array( ... );
$model2Results = $this->paginate("<model2>");
Will return results for <model1> that obey the new conditions/order/limit/whatever you've applied, but your results for <model2> will be based on the original conditions defined for it in $paginate. Your controller will see the updates to $paginate just fine, but it appears $paginate can only be grabbed by Paginator once.
The workaround I have found is to make any and all changes to $paginate BEFORE the first paginate() call, so:
$this->paginate['<model1>']['conditions'] = array( ... );
$this->paginate['<model2>']['conditions'] = array( ... );
$model1Results = $this->paginate('<model1>');
$model2Results = $this->paginate('<model2>');
I've been poking around in PaginatorComponent.php to figure out why things work this way, and any further insight would, of course, be appreciated.

Containable behavior issue with non-standard keys?

I'm working with a legacy database whose keys follow a convention, but not the cake convention, unfortunately, and I've bumped into something odd with the containable behavior -- it's pulling the wrong data. Here's my setup:
TechnologyIncentive belongsTo...
array(
'Incentive' => array(
'className' => 'Incentive',
'type' => 'inner',
'foreignKey' => false, # actually 'incentive_id', but we need to fool Cake
'conditions' => array( 'TechnologyIncentive.incentive_id = Incentive.incentive_id' ),
),
'Technology' => array(
'className' => 'Technology',
'type' => 'inner',
'foreignKey' => false, # actually 'incentive_tech_id', but we need to fool Cake
'conditions' => array( 'TechnologyIncentive.incentive_tech_id = Technology.incentive_tech_id' )
),
);
You can see that I've had to fool Cake into working with my non-standard keys by setting the foreignKey to false and defining the link in my where clause. So far so good.
Now, when I try to run a query from the TechnologyIncentive model:
$this->find( 'all', array(
'contain' => array( 'Incentive', 'Technology' ),
'fields' => array( 'Incentive.name', 'Technology.name', 'TechnologyIncentive.amount' ),
'conditions' => array( /** conditions... */ )
);
Everything works great. Stuff is nicely contained and I get exactly what I'd expect. Then I need to include a TechnologyGroup, which hasMany Technology and things breakdown for some reason.
Now my contain option looks like this:
'contain' => array( 'Incentive', 'Technology' => array( 'TechnologyGroup' ) )
And what I get back is an Incentive record contains an incentive record. That's not entirely surprising since I'm specifying a few fields in one place (the main fields option) and implicitly all fields in the contain option, but what's really weird to me is that they're different incentives. The "contained" incentive is just wrong.
Inspecting the SQL, it looks like the query was run with no effective where clause at all, so everything is being pulled and then artificially limited to a single record. Note the difference between $result['Incentive']['incentive_id'] and $result['Incentive']['Incentive']['incentive_id'].
Array
(
[Incentive] => Array
(
[incentive_id] => MD046
[name] => Incentive name
[category] =>
[Incentive] => Array
(
[incentive_id] => AK004
[code] => AK04F
[version] => 2
[category] => Incentive Category
)
)
)
Has anyone ever bumped into this? It's not a problem until I want to retrieve an extended record (TechnologyGroup). Any thoughts would be much appreciated.
It looks like this was directly attributed to the non-standard keys. The Containable behavior, more so, it seems, that other elements of Cake really wants the convention followed. Moreover, some of the key associations included a double underscore (__) in the field name and that caused other problems.
Kids, don't try this at home. Follow the conventions, even if it means manipulating a legacy database.

Retrieve posts with their latest comment in CakePHP

Say I have a model Post and a model Comment related as follows:
Post hasMany Comment
Comment belongsTo Post
How do use find('all') to retrieve every Post with its associated latest Comment?
I have tried defining a hasOne relationship in Post as:
var $hasOne = array('LatestComment' => array('className' => 'Comment', 'order' => 'LatestComment.created DESC'));
But when I do a Post->find('all') it returns every Post multiple times, once per each Comment, with LatestComment set to the different Comments.
You can add 'limit' => 1 to your array of parameters to only return one comment.
Alternatively, instead of defining another relationship, you can simply limit the number of comments returned when you perform the find, using the Containable behaviour.
$this->Post->find('all',array(
'contain' => array(
'Comment' => array(
'order' => 'Comment.created DESC',
'limit' => 1
)
)
);
This is useful if you want to filter any related sets without defining relationships - by author, or within a date range, for example.
Make sure that you add the Containable behaviour to any model that you reference.
To remove the duplicates you want to use: GROUP BY in your find all query. I believe Cake has a 'group' => option as well.

Resources