Dynamic model relations in CakePHP - cakephp

I'm trying to define the relations for a specific Model depending on environment variables.
Like this:
class Book extends AppModel {
public function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);
if (Configure::read('prefix') == 'admin') {
$this->hasMany['Page'] = array(
// ...
'conditions' => array( /* all pages */ )
);
} else {
$this->hasMany['Page'] = array(
// ...
'conditions' => array( /* only public pages */ )
);
}
}
}
You could argue that I should apply these conditions in the query. But because I'm working with deeply nested relations I wanted to keep conditions centralised.
Now the problem that occurs is: if the Page model has relations to e.g. the Paragraph model and from the BookController I'm trying:
$this->Book->find('first', array(
'conditions' => array('Book.id'=>1),
'contain' => array('Page' => array('Paragraph'))
));
... CakePHP will tell me that Paragraph is not related to the Page model.
If I create the relation by defining a model attribute all goes well:
class Book extends AppModel {
public $hasMany = array(
'Page' => array(
// ...
)
);
}
Why is this? Do I need to manually establish those relations? Is my timing (__construct()) incorrect and should this be done elsewhere?
Kind regards,
Bart

Yes, your timing is incorrect. You should either apply these configuration options before invoking the parent constructor:
if (Configure::read('prefix') == 'admin') {
// ...
}
parent::__construct($id, $table, $ds);
or use Model::bindModel() instead which will create the necessary links:
$this->bindModel(
array('hasMany' => array(
'Page' => array(
// ...
'conditions' => array( /* ... */ )
)
)),
false
);
See also http://book.cakephp.org/...html#creating-and-destroying-associations-on-the-fly

Related

cakephp ACL creates ARO but lft and rght values are null

Using CakePHP 2.2.3
I'm nearly finished with my project and now going back through to setup authorization.
I'm implementing ACL, truncated both the users and groups tables for a fresh start, ran the command to recreate the aco/aro/aros_acos tables and have followed the tutorial.
When I create a group, it creates a corresponding ARO entry but the lft, and rght fields are NULL. I commented out all of my other code in the users/groups models and controllers to try to narrow it down, but it doesn't seem to help.
I will post my code below, with comments and validations removed for the sake of space.
group model:
App::uses('AppModel', 'Model');
class Group extends AppModel {
public $actsAs = array('Acl' => array('type' => 'requester'));
public function parentNode() {
return null;
}
public $hasMany = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'group_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
}
User model:
App::uses('AppModel', 'Model');
App::uses('AuthComponent', 'Controller/Component');
class User extends AppModel {
//setup ACL settings and function
public $actsAs = array('Acl' => array('type' => 'requester'));
public function parentNode() {
if (!$this->id && empty($this->data)) {
return null;
}
if (isset($this->data['User']['group_id'])) {
$groupId = $this->data['User']['group_id'];
} else {
$groupId = $this->field('group_id');
}
if (!$groupId) {
return null;
} else {
return array('Group' => array('id' => $groupId));
}
} // end parentNode()
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias]['password'])) {
$this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['password']);
}
return true;
}
AppController:
App::uses('Controller', 'Controller');
class AppController extends Controller {
public $components = array(
//'Security',
'Acl',
'Auth' => array(
'authorize' => array(
'Actions' => array('actionPath' => 'controllers')
)/*,
'authenticate' => array(
'Form' => array(
'scope' => array('User.activated' => 1 )
)
) */
),
'Session'
);
public $helpers = array(
'Html',
'Text',
'Session',
'Form'
);
/* public function isAuthorized($user = null) {
return true;
} */
public function beforeFilter(){
$this->Auth->loginRedirect = array('controller' => 'products', 'action' => 'index' );
$this->Auth->logoutRedirect = array('controller' => 'products', 'action' => 'index');
$this->Auth->authError = 'You are not allowed to see that.';
}
I even did an ACL implementation on a fresh install of cakephp 2.4.6, and everything works great. I have the projects side by side for comparison but can't find a difference in my ACL setup
Why aren't my lft and rght fields being set in my ARO table?
Short Answer: Remove MVC files associated with ACL tables.
Less Short Answer:
I setup ACL on a fresh install of cake 2.2.3, and everything worked great. Overwrote my code from my user and group models and controllers as well as AppController, and still no go.
I've seen a similar situation when I forget to add $actsAs = array('Tree'); to a model.
I realized I baked controllers/models/views for all ACL tables. DOH! (look for aroscontroller, acoscontroller, etc.)
I removed all the MVC files for these tables and it works great now.
This isn't a typical issue since normally one would add ACL schema after baking, but I started with a database I used on another project and forgot to remove the tables.
I really hope my stupidity helps someone else in this situation.

Associated model conditions being ignored

I have 2 models: Option and Modifier with such Option belongsTo Modifier relation:
public $belongsTo = array(
'Modifier' => array(
'className' => 'Modifier',
'foreignKey' => 'modifier_id',
'conditions' => array('Modifier.type' => '3'),
'fields' => 'Modifier.name',
'order' => 'Modifier.name ASC'
)
);
In OptionsController I try to get list of Modifiers:
$modifiers = $this->Option->Modifier->find('list');
And CakePHP generates SQL without conditions, so I get full list of Modifiers. Why CakePHP 2.4.4 ignores conditions, defined in model belongsTo relation? It also ignores fields and order.
this is the right behavior
when you write:
$modifiers = $this->Option->Modifier->find('list');
you are just accessing Modifier model and not all modifiers related to Option.
to achieve what you want you have to do this:
$modifiers = $this->Option->Modifier->find(
'list',
array('conditions' => array('Modifier.type' => '3')
)
you can also create your own find type (see manual)
class Modifier extends AppModel {
public $findMethods = array('type3' => true);
protected function _findType3($state, $query, $results = array()) {
if ($state === 'before') {
$query['conditions']['Modifier.type'] = 3;
return $query;
}
return $results;
}
}
and in your controller do this
$modifiers = $this->Option->Modifier->find('type3');

CakePHP: User Referral System, how to associate?

I'm building a CakePHP powered website, where users can refer each other, i was thinking about a "Has and Belongs to many" relationship (A user can have multiple referrals, but can be referred only by one)
is this the right way or it's better with another association / road ?
Thanks in advance!
If I'm correct in assuming that a User can refer many, but may only be referred by one, then something like this should suffice for what you're doing:
//User model
$belongsTo = array(
'Referrer' => array(
'className' => 'User',
'foreignKey' = > 'referrer_id'
)
);
$hasMany = array(
'Referree' => array(
'className' => 'User',
'foreignKey' => 'referrer_id'
)
);
Details about Multiple relations to the same model.
An example of how to retrieve the data:
//User model
public $actsAs = array('Containable');
public $recursive = -1; //better to set in AppModel IMO
public function getUser($userId = null) {
if(empty($userId)) return false;
return $this->find('first', array(
'conditions' => array(
$this->alias . '.' . $this->primaryKey => $userId
),
'contain' => array(
'Referrer',
'Referee'
)
));
}

cakephp model with hasmany

I have question in cakephp model,
I want to add dynamic condition in var $hasMany keyword
I want to add condition like current user_id, i got user Id after my login.
var $hasMany = array(
"AskComment"=>array('limit'=>3),
'AskStatistic',
'AskContactsLink',
'AskStatistic',
'AskObject',
'AskLikes'
);
If you want to add dynamic condition in your model, then you might have to bind the model association-ship dynamically into your controller's code. Write the following code into your controller's method for which you want to impose some new condition on the existing/new associated models.
$this->PrimaryModel->bindModel(array('hasMany' => array(
'AskComment' => array(
'className' => 'AskComment',
'foreignKey' => 'primary_id',
'conditions' => array('AskComment.user_id' => $user_id)
)
)
));
Take a look at this link: Creating and destroying associations on the fly. This will surely help you to achieve the same.
I think its better to put your association in the construct function of your Model.
like this:
/**
* #see Model::__construct
*/
public function __construct($id = false, $table = null, $ds = null) {
public $hasMany = array(
'AskComment' => array(
'className' => 'AskComment',
'foreignKey' => 'primary_id',
'conditions' => array(
'AskComment.user_id' => $user_id,
),
),
);
}

CakePHP - How can I find all languages with tongue twisters?

Model
<?php
class Tonguetwister extends AppModel {
var $name = 'Tonguetwister';
//The Associations below have been created with all possible keys, those that are not needed can be removed
var $belongsTo = array(
'language' => array(
'className' => 'language',
'foreignKey' => 'language_alias',
'dependent'=> true
)
);
}
?>
Controller
<?php
class TonguetwistersController extends AppController {
var $name = 'Tonguetwisters';
var $uses = array('Tonguetwister', 'Language');
function index() {
$this->set('languages', $this->Language->find('all'));
}
function view($id = null) {
if (!$id) {
$this->Session->setFlash(__('Invalid tonguetwister', true));
$this->redirect(array('action' => 'index'));
}
$this->set('tonguetwisters', $this->Tonguetwister->find('all', array('conditions' => array('language_alias' => $id))));
}
}
?>
I only want to see languages on index() that have tongue twisters. How can I do this?
There might be a more efficient way, but here's how to pick only unique languages from the Tonguetwister table:
function index() {
$languageList = $this->Tonguetwister->find(
'list',
array(
'fields' => array( 'language_alias', 'language_alias' ),
'group' => 'Tonguetwister.language_alias',
'recursive' => -1
)
);
// $languageList is now an array that holds the language ids
$this->set(
'languages',
$this->Tonguetwister->Language->find(
'all',
array(
'conditions' => array(
'Language.id' => $languageList
)
)
)
);
}
By the way, you don't need to put Language into $uses. Since they have a relation set you can access the Language model with $this->Tonguetwister->Language.
You don't really need to do two SQL queries for this. If the tables are joined on "language_alias" you can do something like this:
function index() {
$this->Language->recursive = 0;
$this->set('languages', $this->Language->find('all', array(
'conditions' => array($this->Language->alias.'.language_alias' => $this->Tonguetwister->alias.'.language_alias')
));
}
You should just do one query that's going to join the tables properly.

Resources