CakePHP: how work the cache groups? - cakephp

I'm trying to use the cache groups, but the examples in the documentation are not very clear for me.
This is my bootstrap:
Cache::config('default', array(
'engine' => $engine,
'duration' => $duration,
'prefix' => $prefix,
'groups' => array('page', 'photo', 'post')
));
We suppose that I have the model, including articles, pages, photos, etc., and in the various actions the data is written to the cache.
For example, PagesController:
public function index() {
$pages = Cache::read($cache = 'pages_index');
if(empty($pages)) {
$pages = $this->Page->find('all');
Cache::write($cache, $pages);
}
$this->set(array(
'pages' => $pages
));
}
This creates the file
tmp/cache/pages_index
The cache is working correctly, the next request will use the cache.
Others actions write the data for the Page model page.
Again PagesController:
public function view($slug = NULL) {
$page = Cache::read($cache = sprintf('pages_view_%s', $slug));
//If the data are not available from the cache
if(empty($page)) {
$page = $this->Page->find('first', array(
'conditions' => array('slug' => $slug)
));
if(empty($page))
throw new NotFoundException(__('Invalid page'));
Cache::write($cache, $page);
}
$this->set(array(
'page' => $page
));
}
This also works correctly.
Now I wish that editing a page being deleted from the cache all the data about pages. The same should happen for the other models (posts, photos, etc).
So, in my Page model:
public function afterSave($created, $options = array()) {
Cache::clearGroup('pages');
}
But this does not work: no files are deleted from the cache.
Where am I doing wrong? What I do not understand?
Thanks!

Because Your group name is 'page', not 'pages'.

Related

Download function not working CakePHP

I read the documentation regarding file downloads, however I can't seem to get this to work.
I have read through questions here as well, and have had no luck.
My function looks as follows:
public function generate($id) {
$this->layout = 'ajax';
$this->Magazine->recursive = 2;
$DistributionLists = $this->Magazine->DistributionList->find('all',
array(
'conditions' => array(
'Magazine.id' => $id
),
'order' => array(
'DistributionList.priority ASC'
)
)
);
$this->set('magazine',$DistributionLists[0]['Magazine']['magazine_name']);
$this->set(compact('DistributionLists'));
}
public function download() {
$this->viewClass = 'Media';
$params = array(
'id' => "Magazine Distribution List.doc",
'name' => "Magazine Distribution List",
'download' => true,
'extension' => 'doc',
'path' => APP . "tmp" . DS
);
$this->set($params);
unlink(APP."tmp".DS);
$this->redirect(array('action'=>'index'));
}
public function afterFilter() {
parent::afterFilter();
if($this->action == 'generate') {
$this->redirect(array('action'=>'download'));
}
}
The reason I have an afterFilter function is because the word document that needs to be downloaded is created in the view file.
Does anyone know why this doesn't work?
You have to remove the call to the redirect method in your download method because it prevents your view from getting "rendered" due to the redirect.

Better approach to do this for security and code management purpose

I am working on a Cakephp 2.3 on a very big project and I'm about to launch my site worldwide.
I have a login system on my app. I am sharing my code because I want to make sure if I am coding right or not ... and also any check for any functions missing or if any advice of adding something or removing something in the code would be greatly appreciated. And also comment in security perspective too...
Do tell me some tips of making my website faster.. for example how to write faster queries or remove unwanted from this blabla
class UsersController extends AppController
{
public $components = array('Cookie');
public function beforeFilter()
{
parent::beforeFilter();
App::uses('Utility', 'Utility');
$this->Auth->allow('index');
$this->Security->requireSecure('login'); // for security
$this->Auth->authenticate = array(
'Authenticate.Cookie' => array(
'fields' => array(
'username' => 'email',
'password' => 'password'
),
'userModel' => 'User',
'scope' => array(
'User.active' => 1
)
),
'Authenticate.MultiColumn' => array(
'fields' => array(
'username' => 'email',
'password' => 'password'
),
'columns' => array(
'email',
'mobileNo'
),
'userModel' => 'User'
)
);
}
public function index()
{
$this->layout = 'logindefault';
if (!$this->Auth->login() || !$this->Auth->loggedIn()) {
$this->redirect(array(
'controller' => 'users',
'action' => 'login'
));
} else {
$this->redirect(array(
'controller' => 'users',
'action' => 'dashboard'
));
}
}
public function login()
{
$this->layout = 'logindefault';
$this->set('title_for_layout', 'Account Login');
if ($this->Auth->login() || $this->Auth->loggedIn()) {
$lastLogin = $this->Auth->User('lastLogin');
if ($lastLogin != null) {
$this->redirect($this->Auth->redirect());
} else {
$this->redirect(array(
'controller' => 'Userinfo',
'action' => 'gettingstarted'
));
}
} else {
if ($this->request->is('post')) {
$mobileNo = $this->request->data['User']['email'];
$mobileNo = Utility::addPlusToMobileNo($mobileNo);
$this->request->data['User']['email'] = $mobileNo;
if ($this->Auth->login() || $this->Auth->loggedIn()) {
if ($this->Session->check('Auth.User')) {
$this->_setCookie($this->Auth->user('idUser'));
$lastLogin = $this->Auth->User('lastLogin');
if ($lastLogin != null) {
$this->redirect(array(
'controller' => 'users',
'action' => 'dashboard'
));
} else {
$this->redirect(array(
'controller' => 'Userinfo',
'action' => 'gettingstarted'
));
}
}
} else {
$this->Session->setFlash('Incorrect Email/Password Combination');
}
}
}
}
protected function _setCookie($id)
{
if (!$this->request->data('User.remember_me')) {
return false;
}
$data = array(
'username' => $this->request->data('User.email'),
'password' => $this->request->data('User.password')
);
$this->Cookie->write('User', $data, true, '1 week');
return true;
}
public function logout()
{
$this->Cookie->delete('User');
$this->redirect($this->Auth->logout());
}
Looks like you're already using the SecurityComponent if you want to secure your app use it everywhere. For AJAX forms white list only the fields you need, dont disable the component!
Put App::uses('Utility', 'Utility'); on top of the file
$mobileNo = Utility::addPlusToMobileNo($mobileNo); should happen in the model beforeSave()
If this is supposed to be used world wide I assume you want translations, this is missing the translation method call __() setFlash('Incorrect Email/Password Combination');
Most of the code CAN and should go into the model layer
Are there unit tests? If not add unit tests, specially test validation of data and false data input. You want ~85%+ Code Coverage for unit tests.
You're not following the CakePHP coding standards
There is no way to tell you more than this without being able to access the whole app code and doing a code review (I could do that). For queries, always just query the data you need, check the generated SQL queries, use DebugKit to check the query times to find slow querys and slowly rendering pages.

CakePHP Pagination. Keeping Model Fat

I currently have this in my Model (Referer Model):
public function getReferers($type = 'today') {
if ($type == 'this_month') {
return $this->_getThisMonthsReferers();
} elseif ($type == 'today') {
return $this->_getTodaysPageReferers();
}
}
private function _getThisMonthsReferers() {
$today = new DateTime();
return $this->Visitor->find('all', array(
'fields' => array(
'Referer.url',
'COUNT(UserRequest.visitor_id) as request_count',
'COUNT(DISTINCT(Visitor.id)) as visitor_count',
'COUNT(UserRequest.visitor_id) / COUNT(DISTINCT(Visitor.id)) as pages_per_visit',
'COUNT(DISTINCT(Visitor.id)) / COUNT(UserRequest.visitor_id) * 100 as percent_new_visit'
),
'joins' => array(
array(
'table' => 'user_requests',
'alias' => 'UserRequest',
'type' => 'RIGHT',
'conditions' => array(
'UserRequest.visitor_id = Visitor.id'
)
)
),
'conditions' => array(
'Visitor.site_id' => $this->Site->id,
'MONTH(UserRequest.created)' => $today->format('m'),
'YEAR(UserRequest.created)' => $today->format('Y')
),
'group' => array(
'url'
)
));
}
The thing is that I how I would paginate this. It will be so easy if just copy my code out of the model and to the controller. The thing is I want the keep the query in my Model.
How is this supposed to be done in CakePHP?
A custom find type is one method. You can find more information here: http://book.cakephp.org/2.0/en/core-libraries/components/pagination.html#custom-query-pagination
To turn your _getThisMonthsReferers into a custom find, follow this http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#creating-custom-find-types
For example:
// model
protected function _findThisMonthsReferers($state, $query, $results = array()) {
if ($state === 'before') {
$query['fields'] = ....
$query['joins'] = ....
return $query;
}
return $results;
}
// controller
public $paginate = array('findType' => 'thisMonthsReferers')
EDIT:
I think it should be :
public $paginate = array('thisMonthsReferers');
However the Solution I used derived from this answer is adding this to the method I am using
$this->paginate = array('thisMonthsReferers');
Since I don't want i used in all my actions. Then paginating the Model like this.
$this->paginate('Visitor);
Instead of returning the results of the find, just return it's array of options:
return array(
'fields' => array(
//...etc
Then use those options to paginate in the controller. More details on this answer of this similar question: Paginate from within a model in CakePHP
It still keeps the model fat (with any logic that might alter the conditions, joins, fields...etc), and the controller skinny, which just uses the returned array as paginate options.

Check for existing controller

I've written static pages component for my application, where admins can dynamically add/edit/remove static content pages. these are saved in the database.
(e.g. you can create a page called "about" and can visit it at myapplication/about)
This is my routing for these pages:
$page = new StaticPage();
$slugs = $page->find('list', array(
'fields' => array('slug'),
'recursive' => -1,
'order' => 'StaticPage.slug DESC',
));
Router::connect('/:slug',
array('controller' => 'static_pages', 'action' => 'display'),
array(
'pass' => array('slug'),
'slug' => implode($slugs, '|')
)
);
Now i have the problem, that when you create a page which slug matches an existing controller (e.g. users), it overwrites the Route to the UsersController.
so i need something like a blacklist or similar: i began to write a validation rule, where i want to check if that controller exists. for cake 1.3 there was a function "loadController" which return false, if the controller did not exist, but for cake 2.x there is no such an function. am i missing this somehow? does it have a new name or is in a utility library now?
Or are there better ways to solve this?
you should try this : http://www.cleverweb.nl/cakephp/list-all-controllers-in-cakephp-2/
and by getting the list of all controllers you can easily exclude the name of controllers
This is my validation method for now:
$route = Router::parse($check['slug']);
$controllerName = Inflector::camelize($route['controller'] . 'Controller');
$aCtrlClasses = App::objects('controller');
foreach ($aCtrlClasses as $controller) {
if ($controller != 'AppController') {
// Load the controller
App::import('Controller', str_replace('Controller', '', $controller));
// Load the ApplicationController (if there is one)
App::import('Controller', 'AppController');
$controllers[] = $controller;
}
}
if (in_array($controllerName, $controllers)) {
return false;
} else {
return true;
}

CakePHP HABTM accessing associated table for display?

For some reason i'm really having a hard time wrapping my head around HABTM associations. I learn best by watching someone do something and explaining why. Anyways, I have 2 tables I want associated, Drugs, and SideEffects. I've created the intermediate table drugs_side_effects(has no data right now). Does cake put the data in that automatically or do I need to do something? The 3.7.6.5 hasAndBelongsToMany (HABTM) from the book didn't specify.
I've set up the models correctly(I think) and am not sure how to proceed at this point. It seems pretty simple. I need to display side_effect from the SideEffects table in a Drug view. I think in the edit_french controller function i'll need something like
$side_effect = $this->Drug->SideEffect->read(
array('SideEffect.id','SideEffect.side_effect'), $id);
$this->set('side_effect',$side_effect);
but I feel like that won't work as expected. Or maybe there's a more efficient way? Any advice or help is appreciated.
Drug Model:
var $hasAndBelongsToMany = array(
'SideEffect' => array(
'className' => 'SideEffect',
'joinTable' => 'drug_side_effects',
'foreignKey' => 'drug_id',
'associationForeignKey' => 'side_effect_id'
)
);
}
?>
SideEffect Model:
var $hasAndBelongsToMany = array(
'Drug' => array(
'className' => 'Drug',
'joinTable' => 'drug_side_effects',
'foreignKey' => 'side_effect_id',
'associationForeignKey' => 'drug_id'
)
);
}
?>
Drugs Controller:
<?php
class DrugsController extends AppController {
var $name = 'Drugs';
var $helpers = array('Html','Form','Paginator');
var $paginate = array(
//'contain' => array('SideEffect'),
//'fields' => array('Drug.id', 'Drug.generic'),
'fields' => array('Drug.id', 'Drug.generic','Drug.date_altered'),
'limit' => 50,
'order' => array(
'Drug.generic' => 'asc'
)
);
function index() {
$data = $this->paginate('Drug');
$alldrugs = $this->set('drugs', $this->Drug->find('all'));
$this->set('drugs', $data);
$this->set('alldrugs', $data);
//$this->set('lessdrugs', $this->paginate());
$this->set('title_for_layout','List of all current drugs');
}
function edit_french($id = null) {
$this->Drug->id = $id;
$drug = $this->Drug->read(
array(
'Drug.id','Drug.generic','Drug.ahl','Drug.aap','Drug.rid','Drug.oral','Drug.mw','Drug.clinical_recommendations'
),
$id
);
$this->set('title_for_layout', 'Translate clinical recommendations - ' . $drug['Drug']['generic']);
$this->set('drug',$drug);
if (empty($this->data)) {
$this->data = $this->Drug->read();
} else {
if ($this->Drug->save($this->data)) {
$this->Session->setFlash('The drug has been updated.');
$this->redirect(array('action' => 'index'));
}
}
}
}
?>
You can pull the related data using ContainableBehavior. To do so, simply run a find on the Drug model and tell it to contain the associated SideEffect data.
$drug = $this->Drug->find('first', array(
'conditions' => array(
'Drug.id' => $id
),
'contain' => array(
'SideEffect'
)
));
You can also set the contain before using read() if you prefer.
$this->Drug->contain(array('SideEffect'));
$drug = $this->Drug->read(null, $id);
Using Containable allows you to gather all associated data in a single find() request.

Resources