CakePHP Fulltext search MySQL with rating - cakephp

I've read that, to be able to rank search results you may query MySQL like this:
SELECT * ,
MATCH (title, body) AGAINST ('$search') AS rating
FROM posts
WHERE MATCH (title, body) AGAINST ('$search')
ORDER BY rating DESC
Is there a way to do this in CakePHP 2.X?
Also, I need to do this while paginating at the same time. So I think I would need to write condition for the paginator, not a direct 'query'.
Thanks for your help!

Use like this it will prevent mysql injection too
array("MATCH(User.current_position) AGAINST(? IN BOOLEAN MODE)" => $srch_arr['text'])

Ok, it took me some time... Since, the key issue was to get a rating on the resulting matches, the complicated part in this query was the specific field:
MATCH (title, body) AGAINST ('$search') AS rating
I figured that I should just write that field in the "field" option, in the pagination array.
The resulting code was the following:
$this->paginate = array(
'limit' => 15,
'fields' => array('*', "MATCH (data) AGAINST ('$q') AS rating"),
'conditions' => "MATCH(SearchIndex.data) AGAINST('$q' IN BOOLEAN MODE)",
'order' => array(
'rating' => 'desc',
),
);
$paginatedResults = $this->paginate('SearchIndex');
And that worked seamlessly!
I think this is the best way to achieve real search results using Cake. Unless someone has a better alternative :)
Searching phrases in between double quotes will give you the results you should expect!

I have used the above database call by Thomas (thank you) and it does work seamlessly.
However the code:
'conditions' => "MATCH(SearchIndex.data) AGAINST('$q' IN BOOLEAN MODE)",
removes the Data Abstraction Layer and opens up your site to SQL injection.
It's probably not quite as good (haven't fully tested it) but try:
'SearchIndex.data LIKE'=>'%'.$search.'%'
I hope this is helpful in someway.

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!

How to retrieve data from model using findByType in cakephp?

I am trying to find records from CallForwardingCondition model using following line of code:
$this->loadModel('CallForwardingCondition');
$this->set('callForwardingCondition', $this->CallForwardingCondition->findByType('list'));
In SQL Dump following query is done when page is reloaded:
SELECT `CallForwardingCondition`.`type`, `CallForwardingCondition`.`description` FROM `vpbx`.`call_forwarding_condition` AS `CallForwardingCondition` WHERE `CallForwardingCondition`.`type` = 'list' LIMIT 1
How can I direct Cakephp to findByType which will result in following query?
SELECT `CallForwardingCondition`.`type`, `CallForwardingCondition`.`description` FROM `vpbx`.`call_forwarding_condition` AS `CallForwardingCondition` WHERE `CallForwardingCondition`.`type` LIKE '%' LIMIT 10
For CakePHP 2.x you need to use find('all') and pass it the required conditions and limit:-
$result = $this->CallForwardingCondition->find('all',[
'conditions' => ['CallForwardingCondition.type Like' => '%'],
'limit' => 10
);
findByType is a special find method that will only return the first record matching the type passed as the find method's parameter which is why it isn't returning what you want. You can read more about the findBy magic functions in the official docs.
try this:
$result = $this-> CallForwardingCondition ->find('all',['limit'=>10,'conditions'=>['CallForwardingCondition.type Like'=>'%'])->toArray();
I don't think you can't use findBy with limit. If you want to use limit, you must use findAllBy<fieldName>. Even findAllBy<fieldName>, you can't use LIKE.
This is findAllBy from cakephp
findAllBy<fieldName>(string $value, array $fields, array $order, int $limit, int $page, int $recursive)
So if you want to covert it, you must do the following way;
$this->CallForwardingCondition->findAllByType('something',['CallForwardingCondition.*'],['CallForwardingCondition.id'=>'desc'],'10');
This is findBy from cakephp doc.
findBy<fieldName>(string $value[, mixed $fields[, mixed $order]]);
Hope this help for you.

cakephp paginator extremely slow

I have a cakephp application, 2.4, and I'm having issues with the Paginator component. First off, it's not the database, it's definitely the execution of parsing the query results. I have DebugKit installed and can see that my mysql query for the paginated data takes a whole 2 ms. The table has 2.5 million records of messages, and 500,000 users. Obviously proper indexing is in place. But, the controller action is taking 6167.82 ms. So, here's my controller action:
$this->Paginator->settings = array(
'Message' => array(
'fields' => array(
'Recipient.username',
'Recipient.profile_photo',
'Recipient.id',
'Message.*'
),
'joins' => array(array(
'table' => 'users',
'alias' => 'Recipient',
'type' => 'LEFT',
'conditions' => array(
'Recipient.id = `Message`.`recipient_id`'
)
)),
'conditions' => array(
'Message.sender_id' => $this->Auth->user('id'),
'Message.deleted_by_sender' => '0'
),
'limit' => 10,
'order' => 'Message.id DESC',
'recursive' => -1
)
);
$sents = $this->Paginator->paginate( 'Message' );
$this->set( 'sents', $sents );
$this->view = 'index';
I've google this and searched stack overflow. The majority of the responses are for poor mysql optimization which isn't my case. The other half of the responses suggest containable. So, I tried containable. Using contain was actually slower because it tried to grab even more data from the user's field than just the username, photo, and id. Then when cake built the array from the query results it executed nearly 500 ms slower with containable because of the extra user data I'm assuming.
I'm going to now dig into the cake Paginator component and see why it's taking so long to build the response. I'm hoping someone beats me to it and has a good solution to help speed this up.
My web server is running ubuntu 12.04 with 3gb ram, apache and mod_php with apc installed and working for the model and core cache. The database is on a separate server. I also have a redis server persisting other user data and the cake session data. There is plenty of power here to parse 10 records from a mysql query containing about a dozen rows.
EDIT: ANSWER
As suggested first by Ilie Pandia there was something else happening, such as a callback, that was slowing down the pagination. This was actually unrelated to the pagination component. The Recipient model had a behavior that loaded an sdk in the setup callback for a 3rd party service. That service was taking several seconds to respond. This happened when the linkedModel in the query was loaded to filter the results. Hopefully anyone else looking for reasons why cake might be performing poorly will also look at the callbacks on models in the application and plugins.
I see no reason for this to run slow at all.
So this suggests that there are some callback installed (either in the model or the controller) that do additional processing and inflate the action time so much.
That is assuming that there is nothing else in the controller but what your wrote.
You could actually measure the time of the paginate call itself and I think you will find that it is very fast. So the bottle neck is elsewhere in the code.
PS: You could also try to disable DebugKit for a while. Introspection may take very long for some particular cases.
Install DebugKit for your application.
And inspect which query is taking too much time. From there, you should be able to track the bottleneck.

Forcing left join on my simple inner join query

The site is already built from years. I am doing some modifications to it. It has controllers "Posts" and "Topics". On the topics page all recent posts are displayed. So it is a simple find of posts from "Posts" table. The fetch is not all assiciated to topic as we are showing all "Posts" and not "Topic" specific "Posts". In Topics controller,
App::import('Model', 'Post'); and $Posts = new Post;
are added and I call the action "getDiscussions" from Model "Post". The problem is that though I am not using any left join with "Topics" and "Users" (which is one more model used to display user details with post), the query is adding Left joins with two tables and that is giving a wrong result.
Please help.
Many Thanks
Yukti
I think it is better to try to find out why cake is doing these "joins", rather than performing yet another forced query, so you might want to look into your model associations to check dependancies. Also, you can set $this->Posts->recursive = -1; to make sure you don't import unwanted stuff.
In the end, I am not sure what query you are trying to perform, so I can't help you further. However, here is a sample of a query I have successfully used to perform a left join, as I've noticed that many examples I found while writing my own were bugged:
$user_record = $this->User->find('first', array(
'conditions' => array('`Openidurl`.`openid`' => $openid),
'joins' => array(
array(
'table' => 'openidurls',
'alias' => 'Openidurl',
'type' => 'LEFT',
'foreignKey' => 'user_id',
'conditions'=> array('`Openidurl`.`user_id` = `User`.`id`')
)
)
)
);

Select from a table where associated table fieldname = 'x' in Cake PHP

In CakePHP, I have two tables, Countries & Networks. They have a HABTM relationship and are joined by countries_networks.
I'm trying to get all countries from the countries table where the 'name' field in Networks = 'o2'
I've realised I can't do this using a basic find(), so I've been experimenting with the containable behaviour. I have managed to restrict the returned data, but it looks as though 'containable' doesn't exactly work as I want. Heres my code:
$countries = $this->Country->find('all', array('contain' => array(
'Network' => array(
'conditions' => array('Network.name =' => "o2"),
)
)));
This query however returns ALL countries, and the Network.name if its 'o2'. What I really need to do is return ONLY the countries that have a Network.name of 'o2', and no others.
Can anyone help? Thanks.
"=' =>"
What is it? there is no needed to use "=" symbol after "Network.name"
Your query returns you exactly what your ask.
Try to select from Network.
$countries = $this->Country->Network->find('first', array(
'conditions' => array('Network.name' => "o2"),
'contain' => array('Country')
));
$countries = $countries['Country'];
You should be able to do something like this:
$this->Country->hasAndBelongsToMany['Network']['conditions'] = array('Network.name'=>'o2');
$myCountries = $this->Country->find('all');
I haven't tested this, but it should get you pretty close to where you need to be.
Also, bear in mind that changing the hasAndBelongsToMany array like this will affect all subsequent queries against that model during the current page load (but it will be reset the next time the model is instantiated).
I'm on my way out the door, so sorry for the brief explanation.
I think what you want is a HABTM relationship, but to be able to filter based on the associated model's data. To do this, check out the "Containable" behavior in Cake's manual. Pretty sure that's what you're after.

Resources